mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add an option to serve ES6 JS to clients (#10474)
* Add an option to serve ES6 JS to clients * Rename es6 to latest * Fixes * Serve JS vrsions from separate dirs * Revert websocket API change * Update frontend to 20171110.0 * websocket: move request to constructor
This commit is contained in:
parent
1c36e2f586
commit
5e92fa3404
@ -9,6 +9,7 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -21,21 +22,19 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
REQUIREMENTS = ['home-assistant-frontend==20171106.0']
|
REQUIREMENTS = ['home-assistant-frontend==20171110.0']
|
||||||
|
|
||||||
DOMAIN = 'frontend'
|
DOMAIN = 'frontend'
|
||||||
DEPENDENCIES = ['api', 'websocket_api']
|
DEPENDENCIES = ['api', 'websocket_api', 'http']
|
||||||
|
|
||||||
URL_PANEL_COMPONENT = '/frontend/panels/{}.html'
|
|
||||||
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
|
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
|
||||||
|
|
||||||
POLYMER_PATH = os.path.join(os.path.dirname(__file__),
|
|
||||||
'home-assistant-polymer/')
|
|
||||||
FINAL_PATH = os.path.join(POLYMER_PATH, 'final')
|
|
||||||
|
|
||||||
CONF_THEMES = 'themes'
|
CONF_THEMES = 'themes'
|
||||||
CONF_EXTRA_HTML_URL = 'extra_html_url'
|
CONF_EXTRA_HTML_URL = 'extra_html_url'
|
||||||
CONF_FRONTEND_REPO = 'development_repo'
|
CONF_FRONTEND_REPO = 'development_repo'
|
||||||
|
CONF_JS_VERSION = 'javascript_version'
|
||||||
|
JS_DEFAULT_OPTION = 'es5'
|
||||||
|
JS_OPTIONS = ['es5', 'latest', 'auto']
|
||||||
|
|
||||||
DEFAULT_THEME_COLOR = '#03A9F4'
|
DEFAULT_THEME_COLOR = '#03A9F4'
|
||||||
|
|
||||||
@ -61,6 +60,7 @@ for size in (192, 384, 512, 1024):
|
|||||||
|
|
||||||
DATA_FINALIZE_PANEL = 'frontend_finalize_panel'
|
DATA_FINALIZE_PANEL = 'frontend_finalize_panel'
|
||||||
DATA_PANELS = 'frontend_panels'
|
DATA_PANELS = 'frontend_panels'
|
||||||
|
DATA_JS_VERSION = 'frontend_js_version'
|
||||||
DATA_EXTRA_HTML_URL = 'frontend_extra_html_url'
|
DATA_EXTRA_HTML_URL = 'frontend_extra_html_url'
|
||||||
DATA_THEMES = 'frontend_themes'
|
DATA_THEMES = 'frontend_themes'
|
||||||
DATA_DEFAULT_THEME = 'frontend_default_theme'
|
DATA_DEFAULT_THEME = 'frontend_default_theme'
|
||||||
@ -68,8 +68,6 @@ DEFAULT_THEME = 'default'
|
|||||||
|
|
||||||
PRIMARY_COLOR = 'primary-color'
|
PRIMARY_COLOR = 'primary-color'
|
||||||
|
|
||||||
# To keep track we don't register a component twice (gives a warning)
|
|
||||||
# _REGISTERED_COMPONENTS = set()
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
@ -80,6 +78,8 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
}),
|
}),
|
||||||
vol.Optional(CONF_EXTRA_HTML_URL):
|
vol.Optional(CONF_EXTRA_HTML_URL):
|
||||||
vol.All(cv.ensure_list, [cv.string]),
|
vol.All(cv.ensure_list, [cv.string]),
|
||||||
|
vol.Optional(CONF_JS_VERSION, default=JS_DEFAULT_OPTION):
|
||||||
|
vol.In(JS_OPTIONS)
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
@ -102,8 +102,9 @@ class AbstractPanel:
|
|||||||
# Title to show in the sidebar (optional)
|
# Title to show in the sidebar (optional)
|
||||||
sidebar_title = None
|
sidebar_title = None
|
||||||
|
|
||||||
# Url to the webcomponent
|
# Url to the webcomponent (depending on JS version)
|
||||||
webcomponent_url = None
|
webcomponent_url_es5 = None
|
||||||
|
webcomponent_url_latest = None
|
||||||
|
|
||||||
# Url to show the panel in the frontend
|
# Url to show the panel in the frontend
|
||||||
frontend_url_path = None
|
frontend_url_path = None
|
||||||
@ -135,16 +136,20 @@ class AbstractPanel:
|
|||||||
'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path),
|
'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path),
|
||||||
index_view.get)
|
index_view.get)
|
||||||
|
|
||||||
def as_dict(self):
|
def to_response(self, hass, request):
|
||||||
"""Panel as dictionary."""
|
"""Panel as dictionary."""
|
||||||
return {
|
result = {
|
||||||
'component_name': self.component_name,
|
'component_name': self.component_name,
|
||||||
'icon': self.sidebar_icon,
|
'icon': self.sidebar_icon,
|
||||||
'title': self.sidebar_title,
|
'title': self.sidebar_title,
|
||||||
'url': self.webcomponent_url,
|
|
||||||
'url_path': self.frontend_url_path,
|
'url_path': self.frontend_url_path,
|
||||||
'config': self.config,
|
'config': self.config,
|
||||||
}
|
}
|
||||||
|
if _is_latest(hass.data[DATA_JS_VERSION], request):
|
||||||
|
result['url'] = self.webcomponent_url_latest
|
||||||
|
else:
|
||||||
|
result['url'] = self.webcomponent_url_es5
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class BuiltInPanel(AbstractPanel):
|
class BuiltInPanel(AbstractPanel):
|
||||||
@ -170,15 +175,19 @@ class BuiltInPanel(AbstractPanel):
|
|||||||
|
|
||||||
if frontend_repository_path is None:
|
if frontend_repository_path is None:
|
||||||
import hass_frontend
|
import hass_frontend
|
||||||
|
import hass_frontend_es5
|
||||||
|
|
||||||
self.webcomponent_url = \
|
self.webcomponent_url_latest = \
|
||||||
'/static/panels/ha-panel-{}-{}.html'.format(
|
'/frontend_latest/panels/ha-panel-{}-{}.html'.format(
|
||||||
self.component_name,
|
self.component_name,
|
||||||
hass_frontend.FINGERPRINTS[panel_path])
|
hass_frontend.FINGERPRINTS[panel_path])
|
||||||
|
self.webcomponent_url_es5 = \
|
||||||
|
'/frontend_es5/panels/ha-panel-{}-{}.html'.format(
|
||||||
|
self.component_name,
|
||||||
|
hass_frontend_es5.FINGERPRINTS[panel_path])
|
||||||
else:
|
else:
|
||||||
# Dev mode
|
# Dev mode
|
||||||
self.webcomponent_url = \
|
self.webcomponent_url_es5 = self.webcomponent_url_latest = \
|
||||||
'/home-assistant-polymer/panels/{}/ha-panel-{}.html'.format(
|
'/home-assistant-polymer/panels/{}/ha-panel-{}.html'.format(
|
||||||
self.component_name, self.component_name)
|
self.component_name, self.component_name)
|
||||||
|
|
||||||
@ -208,18 +217,20 @@ class ExternalPanel(AbstractPanel):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if self.md5 is None:
|
if self.md5 is None:
|
||||||
yield from hass.async_add_job(_fingerprint, self.path)
|
self.md5 = yield from hass.async_add_job(
|
||||||
|
_fingerprint, self.path)
|
||||||
except OSError:
|
except OSError:
|
||||||
_LOGGER.error('Cannot find or access %s at %s',
|
_LOGGER.error('Cannot find or access %s at %s',
|
||||||
self.component_name, self.path)
|
self.component_name, self.path)
|
||||||
hass.data[DATA_PANELS].pop(self.frontend_url_path)
|
hass.data[DATA_PANELS].pop(self.frontend_url_path)
|
||||||
|
return
|
||||||
|
|
||||||
self.webcomponent_url = \
|
self.webcomponent_url_es5 = self.webcomponent_url_latest = \
|
||||||
URL_PANEL_COMPONENT_FP.format(self.component_name, self.md5)
|
URL_PANEL_COMPONENT_FP.format(self.component_name, self.md5)
|
||||||
|
|
||||||
if self.component_name not in self.REGISTERED_COMPONENTS:
|
if self.component_name not in self.REGISTERED_COMPONENTS:
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
self.webcomponent_url, self.path,
|
self.webcomponent_url_latest, self.path,
|
||||||
# if path is None, we're in prod mode, so cache static assets
|
# if path is None, we're in prod mode, so cache static assets
|
||||||
frontend_repository_path is None)
|
frontend_repository_path is None)
|
||||||
self.REGISTERED_COMPONENTS.add(self.component_name)
|
self.REGISTERED_COMPONENTS.add(self.component_name)
|
||||||
@ -281,31 +292,50 @@ def async_setup(hass, config):
|
|||||||
|
|
||||||
repo_path = conf.get(CONF_FRONTEND_REPO)
|
repo_path = conf.get(CONF_FRONTEND_REPO)
|
||||||
is_dev = repo_path is not None
|
is_dev = repo_path is not None
|
||||||
|
hass.data[DATA_JS_VERSION] = js_version = conf.get(CONF_JS_VERSION)
|
||||||
|
|
||||||
if is_dev:
|
if is_dev:
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
"/home-assistant-polymer", repo_path, False)
|
"/home-assistant-polymer", repo_path, False)
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
"/static/translations",
|
"/static/translations",
|
||||||
os.path.join(repo_path, "build/translations"), False)
|
os.path.join(repo_path, "build-translations"), False)
|
||||||
sw_path = os.path.join(repo_path, "build/service_worker.js")
|
sw_path_es5 = os.path.join(repo_path, "build-es5/service_worker.js")
|
||||||
|
sw_path_latest = os.path.join(repo_path, "build/service_worker.js")
|
||||||
static_path = os.path.join(repo_path, 'hass_frontend')
|
static_path = os.path.join(repo_path, 'hass_frontend')
|
||||||
|
frontend_es5_path = os.path.join(repo_path, 'build-es5')
|
||||||
|
frontend_latest_path = os.path.join(repo_path, 'build')
|
||||||
else:
|
else:
|
||||||
import hass_frontend
|
import hass_frontend
|
||||||
frontend_path = hass_frontend.where()
|
import hass_frontend_es5
|
||||||
sw_path = os.path.join(frontend_path, "service_worker.js")
|
sw_path_es5 = os.path.join(hass_frontend_es5.where(),
|
||||||
static_path = frontend_path
|
"service_worker.js")
|
||||||
|
sw_path_latest = os.path.join(hass_frontend.where(),
|
||||||
|
"service_worker.js")
|
||||||
|
# /static points to dir with files that are JS-type agnostic.
|
||||||
|
# ES5 files are served from /frontend_es5.
|
||||||
|
# ES6 files are served from /frontend_latest.
|
||||||
|
static_path = hass_frontend.where()
|
||||||
|
frontend_es5_path = hass_frontend_es5.where()
|
||||||
|
frontend_latest_path = static_path
|
||||||
|
|
||||||
hass.http.register_static_path("/service_worker.js", sw_path, False)
|
hass.http.register_static_path(
|
||||||
|
"/service_worker_es5.js", sw_path_es5, False)
|
||||||
|
hass.http.register_static_path(
|
||||||
|
"/service_worker.js", sw_path_latest, False)
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
"/robots.txt", os.path.join(static_path, "robots.txt"), not is_dev)
|
"/robots.txt", os.path.join(static_path, "robots.txt"), not is_dev)
|
||||||
hass.http.register_static_path("/static", static_path, not is_dev)
|
hass.http.register_static_path("/static", static_path, not is_dev)
|
||||||
|
hass.http.register_static_path(
|
||||||
|
"/frontend_latest", frontend_latest_path, not is_dev)
|
||||||
|
hass.http.register_static_path(
|
||||||
|
"/frontend_es5", frontend_es5_path, not is_dev)
|
||||||
|
|
||||||
local = hass.config.path('www')
|
local = hass.config.path('www')
|
||||||
if os.path.isdir(local):
|
if os.path.isdir(local):
|
||||||
hass.http.register_static_path("/local", local, not is_dev)
|
hass.http.register_static_path("/local", local, not is_dev)
|
||||||
|
|
||||||
index_view = IndexView(is_dev)
|
index_view = IndexView(is_dev, js_version)
|
||||||
hass.http.register_view(index_view)
|
hass.http.register_view(index_view)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -405,7 +435,7 @@ class IndexView(HomeAssistantView):
|
|||||||
requires_auth = False
|
requires_auth = False
|
||||||
extra_urls = ['/states', '/states/{extra}']
|
extra_urls = ['/states', '/states/{extra}']
|
||||||
|
|
||||||
def __init__(self, use_repo):
|
def __init__(self, use_repo, js_option):
|
||||||
"""Initialize the frontend view."""
|
"""Initialize the frontend view."""
|
||||||
from jinja2 import FileSystemLoader, Environment
|
from jinja2 import FileSystemLoader, Environment
|
||||||
|
|
||||||
@ -416,27 +446,37 @@ class IndexView(HomeAssistantView):
|
|||||||
os.path.join(os.path.dirname(__file__), 'templates/')
|
os.path.join(os.path.dirname(__file__), 'templates/')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.js_option = js_option
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def get(self, request, extra=None):
|
def get(self, request, extra=None):
|
||||||
"""Serve the index view."""
|
"""Serve the index view."""
|
||||||
hass = request.app['hass']
|
hass = request.app['hass']
|
||||||
|
latest = _is_latest(self.js_option, request)
|
||||||
|
compatibility_url = None
|
||||||
|
|
||||||
if self.use_repo:
|
if self.use_repo:
|
||||||
core_url = '/home-assistant-polymer/build/core.js'
|
core_url = '/home-assistant-polymer/{}/core.js'.format(
|
||||||
compatibility_url = \
|
'build' if latest else 'build-es5')
|
||||||
'/home-assistant-polymer/build/compatibility.js'
|
|
||||||
ui_url = '/home-assistant-polymer/src/home-assistant.html'
|
ui_url = '/home-assistant-polymer/src/home-assistant.html'
|
||||||
icons_fp = ''
|
icons_fp = ''
|
||||||
icons_url = '/static/mdi.html'
|
icons_url = '/static/mdi.html'
|
||||||
else:
|
else:
|
||||||
|
if latest:
|
||||||
import hass_frontend
|
import hass_frontend
|
||||||
core_url = '/static/core-{}.js'.format(
|
core_url = '/frontend_latest/core-{}.js'.format(
|
||||||
hass_frontend.FINGERPRINTS['core.js'])
|
hass_frontend.FINGERPRINTS['core.js'])
|
||||||
compatibility_url = '/static/compatibility-{}.js'.format(
|
ui_url = '/frontend_latest/frontend-{}.html'.format(
|
||||||
hass_frontend.FINGERPRINTS['compatibility.js'])
|
|
||||||
ui_url = '/static/frontend-{}.html'.format(
|
|
||||||
hass_frontend.FINGERPRINTS['frontend.html'])
|
hass_frontend.FINGERPRINTS['frontend.html'])
|
||||||
|
else:
|
||||||
|
import hass_frontend_es5
|
||||||
|
core_url = '/frontend_es5/core-{}.js'.format(
|
||||||
|
hass_frontend_es5.FINGERPRINTS['core.js'])
|
||||||
|
compatibility_url = '/frontend_es5/compatibility-{}.js'.format(
|
||||||
|
hass_frontend_es5.FINGERPRINTS['compatibility.js'])
|
||||||
|
ui_url = '/frontend_es5/frontend-{}.html'.format(
|
||||||
|
hass_frontend_es5.FINGERPRINTS['frontend.html'])
|
||||||
|
import hass_frontend
|
||||||
icons_fp = '-{}'.format(hass_frontend.FINGERPRINTS['mdi.html'])
|
icons_fp = '-{}'.format(hass_frontend.FINGERPRINTS['mdi.html'])
|
||||||
icons_url = '/static/mdi{}.html'.format(icons_fp)
|
icons_url = '/static/mdi{}.html'.format(icons_fp)
|
||||||
|
|
||||||
@ -447,8 +487,10 @@ class IndexView(HomeAssistantView):
|
|||||||
|
|
||||||
if panel == 'states':
|
if panel == 'states':
|
||||||
panel_url = ''
|
panel_url = ''
|
||||||
|
elif latest:
|
||||||
|
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_latest
|
||||||
else:
|
else:
|
||||||
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url
|
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_es5
|
||||||
|
|
||||||
no_auth = 'true'
|
no_auth = 'true'
|
||||||
if hass.config.api.api_password and not is_trusted_ip(request):
|
if hass.config.api.api_password and not is_trusted_ip(request):
|
||||||
@ -468,7 +510,10 @@ class IndexView(HomeAssistantView):
|
|||||||
panel_url=panel_url, panels=hass.data[DATA_PANELS],
|
panel_url=panel_url, panels=hass.data[DATA_PANELS],
|
||||||
dev_mode=self.use_repo,
|
dev_mode=self.use_repo,
|
||||||
theme_color=MANIFEST_JSON['theme_color'],
|
theme_color=MANIFEST_JSON['theme_color'],
|
||||||
extra_urls=hass.data[DATA_EXTRA_HTML_URL])
|
extra_urls=hass.data[DATA_EXTRA_HTML_URL],
|
||||||
|
latest=latest,
|
||||||
|
service_worker_name='/service_worker.js' if latest else
|
||||||
|
'/service_worker_es5.js')
|
||||||
|
|
||||||
return web.Response(text=resp, content_type='text/html')
|
return web.Response(text=resp, content_type='text/html')
|
||||||
|
|
||||||
@ -509,3 +554,20 @@ def _fingerprint(path):
|
|||||||
"""Fingerprint a file."""
|
"""Fingerprint a file."""
|
||||||
with open(path) as fil:
|
with open(path) as fil:
|
||||||
return hashlib.md5(fil.read().encode('utf-8')).hexdigest()
|
return hashlib.md5(fil.read().encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _is_latest(js_option, request):
|
||||||
|
"""
|
||||||
|
Return whether we should serve latest untranspiled code.
|
||||||
|
|
||||||
|
Set according to user's preference and URL override.
|
||||||
|
"""
|
||||||
|
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')
|
||||||
|
@ -78,11 +78,11 @@
|
|||||||
<a href='/'>TRY AGAIN</a>
|
<a href='/'>TRY AGAIN</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<home-assistant icons='{{ icons }}'></home-assistant>
|
<home-assistant></home-assistant>
|
||||||
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}
|
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> -#}
|
||||||
|
{% if not latest -%}
|
||||||
<script>
|
<script>
|
||||||
var compatibilityRequired = (
|
var compatibilityRequired = (typeof Object.assign != 'function');
|
||||||
typeof Object.assign != 'function');
|
|
||||||
if (compatibilityRequired) {
|
if (compatibilityRequired) {
|
||||||
var e = document.createElement('script');
|
var e = document.createElement('script');
|
||||||
e.onerror = initError;
|
e.onerror = initError;
|
||||||
@ -90,10 +90,11 @@
|
|||||||
document.head.appendChild(e);
|
document.head.appendChild(e);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
{% endif -%}
|
||||||
<script src='{{ core_url }}'></script>
|
<script src='{{ core_url }}'></script>
|
||||||
{% if not dev_mode %}
|
{% if not dev_mode and not latest -%}
|
||||||
<script src='/static/custom-elements-es5-adapter.js'></script>
|
<script src='/frontend_es5/custom-elements-es5-adapter.js'></script>
|
||||||
{% endif %}
|
{% endif -%}
|
||||||
<script>
|
<script>
|
||||||
var webComponentsSupported = (
|
var webComponentsSupported = (
|
||||||
'customElements' in window &&
|
'customElements' in window &&
|
||||||
@ -105,6 +106,11 @@
|
|||||||
e.src = '/static/webcomponents-lite.js';
|
e.src = '/static/webcomponents-lite.js';
|
||||||
document.head.appendChild(e);
|
document.head.appendChild(e);
|
||||||
}
|
}
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('{{ service_worker_name }}');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
|
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
|
||||||
{% if panel_url -%}
|
{% if panel_url -%}
|
||||||
|
@ -262,7 +262,6 @@ class HomeAssistantWSGI(object):
|
|||||||
resource = CachingStaticResource
|
resource = CachingStaticResource
|
||||||
else:
|
else:
|
||||||
resource = web.StaticResource
|
resource = web.StaticResource
|
||||||
|
|
||||||
self.app.router.register_resource(resource(url_path, path))
|
self.app.router.register_resource(resource(url_path, path))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -65,7 +65,8 @@ class CachingFileResponse(FileResponse):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def staticresource_middleware(request, handler):
|
def staticresource_middleware(request, handler):
|
||||||
"""Middleware to strip out fingerprint from fingerprinted assets."""
|
"""Middleware to strip out fingerprint from fingerprinted assets."""
|
||||||
if not request.path.startswith('/static/'):
|
path = request.path
|
||||||
|
if not path.startswith('/static/') and not path.startswith('/frontend'):
|
||||||
return handler(request)
|
return handler(request)
|
||||||
|
|
||||||
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
|
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
|
||||||
|
@ -202,15 +202,16 @@ class WebsocketAPIView(HomeAssistantView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""Handle an incoming websocket connection."""
|
"""Handle an incoming websocket connection."""
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
return ActiveConnection(request.app['hass']).handle(request)
|
return ActiveConnection(request.app['hass'], request).handle()
|
||||||
|
|
||||||
|
|
||||||
class ActiveConnection:
|
class ActiveConnection:
|
||||||
"""Handle an active websocket client connection."""
|
"""Handle an active websocket client connection."""
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass, request):
|
||||||
"""Initialize an active connection."""
|
"""Initialize an active connection."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
self.request = request
|
||||||
self.wsock = None
|
self.wsock = None
|
||||||
self.event_listeners = {}
|
self.event_listeners = {}
|
||||||
self.to_write = asyncio.Queue(maxsize=MAX_PENDING_MSG, loop=hass.loop)
|
self.to_write = asyncio.Queue(maxsize=MAX_PENDING_MSG, loop=hass.loop)
|
||||||
@ -259,8 +260,9 @@ class ActiveConnection:
|
|||||||
self._writer_task.cancel()
|
self._writer_task.cancel()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def handle(self, request):
|
def handle(self):
|
||||||
"""Handle the websocket connection."""
|
"""Handle the websocket connection."""
|
||||||
|
request = self.request
|
||||||
wsock = self.wsock = web.WebSocketResponse()
|
wsock = self.wsock = web.WebSocketResponse()
|
||||||
yield from wsock.prepare(request)
|
yield from wsock.prepare(request)
|
||||||
self.debug("Connected")
|
self.debug("Connected")
|
||||||
@ -350,7 +352,7 @@ class ActiveConnection:
|
|||||||
if wsock.closed:
|
if wsock.closed:
|
||||||
self.debug("Connection closed by client")
|
self.debug("Connection closed by client")
|
||||||
else:
|
else:
|
||||||
self.log_error("Unexpected TypeError", msg)
|
_LOGGER.exception("Unexpected TypeError: %s", msg)
|
||||||
|
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
msg = "Received invalid JSON"
|
msg = "Received invalid JSON"
|
||||||
@ -483,9 +485,14 @@ class ActiveConnection:
|
|||||||
Async friendly.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
msg = GET_PANELS_MESSAGE_SCHEMA(msg)
|
msg = GET_PANELS_MESSAGE_SCHEMA(msg)
|
||||||
|
panels = {
|
||||||
|
panel:
|
||||||
|
self.hass.data[frontend.DATA_PANELS][panel].to_response(
|
||||||
|
self.hass, self.request)
|
||||||
|
for panel in self.hass.data[frontend.DATA_PANELS]}
|
||||||
|
|
||||||
self.to_write.put_nowait(result_message(
|
self.to_write.put_nowait(result_message(
|
||||||
msg['id'], self.hass.data[frontend.DATA_PANELS]))
|
msg['id'], panels))
|
||||||
|
|
||||||
def handle_ping(self, msg):
|
def handle_ping(self, msg):
|
||||||
"""Handle ping command.
|
"""Handle ping command.
|
||||||
|
@ -330,7 +330,7 @@ hipnotify==1.0.8
|
|||||||
holidays==0.8.1
|
holidays==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20171106.0
|
home-assistant-frontend==20171110.0
|
||||||
|
|
||||||
# homeassistant.components.camera.onvif
|
# homeassistant.components.camera.onvif
|
||||||
http://github.com/tgaugry/suds-passworddigest-py3/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip#suds-passworddigest-py3==0.1.2a
|
http://github.com/tgaugry/suds-passworddigest-py3/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip#suds-passworddigest-py3==0.1.2a
|
||||||
|
@ -74,7 +74,7 @@ hbmqtt==0.8
|
|||||||
holidays==0.8.1
|
holidays==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20171106.0
|
home-assistant-frontend==20171110.0
|
||||||
|
|
||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
# homeassistant.components.sensor.influxdb
|
# homeassistant.components.sensor.influxdb
|
||||||
|
@ -52,7 +52,7 @@ def test_frontend_and_static(mock_http_client):
|
|||||||
|
|
||||||
# Test we can retrieve frontend.js
|
# Test we can retrieve frontend.js
|
||||||
frontendjs = re.search(
|
frontendjs = re.search(
|
||||||
r'(?P<app>\/static\/frontend-[A-Za-z0-9]{32}.html)', text)
|
r'(?P<app>\/frontend_es5\/frontend-[A-Za-z0-9]{32}.html)', text)
|
||||||
|
|
||||||
assert frontendjs is not None
|
assert frontendjs is not None
|
||||||
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
|
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
|
||||||
@ -63,6 +63,10 @@ def test_frontend_and_static(mock_http_client):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_dont_cache_service_worker(mock_http_client):
|
def test_dont_cache_service_worker(mock_http_client):
|
||||||
"""Test that we don't cache the service worker."""
|
"""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')
|
resp = yield from mock_http_client.get('/service_worker.js')
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
assert 'cache-control' not in resp.headers
|
assert 'cache-control' not in resp.headers
|
||||||
|
@ -33,7 +33,7 @@ class TestPanelIframe(unittest.TestCase):
|
|||||||
'panel_iframe': conf
|
'panel_iframe': conf
|
||||||
})
|
})
|
||||||
|
|
||||||
@patch.dict('hass_frontend.FINGERPRINTS',
|
@patch.dict('hass_frontend_es5.FINGERPRINTS',
|
||||||
{'panels/ha-panel-iframe.html': 'md5md5'})
|
{'panels/ha-panel-iframe.html': 'md5md5'})
|
||||||
def test_correct_config(self):
|
def test_correct_config(self):
|
||||||
"""Test correct config."""
|
"""Test correct config."""
|
||||||
@ -55,20 +55,20 @@ class TestPanelIframe(unittest.TestCase):
|
|||||||
|
|
||||||
panels = self.hass.data[frontend.DATA_PANELS]
|
panels = self.hass.data[frontend.DATA_PANELS]
|
||||||
|
|
||||||
assert panels.get('router').as_dict() == {
|
assert panels.get('router').to_response(self.hass, None) == {
|
||||||
'component_name': 'iframe',
|
'component_name': 'iframe',
|
||||||
'config': {'url': 'http://192.168.1.1'},
|
'config': {'url': 'http://192.168.1.1'},
|
||||||
'icon': 'mdi:network-wireless',
|
'icon': 'mdi:network-wireless',
|
||||||
'title': 'Router',
|
'title': 'Router',
|
||||||
'url': '/static/panels/ha-panel-iframe-md5md5.html',
|
'url': '/frontend_es5/panels/ha-panel-iframe-md5md5.html',
|
||||||
'url_path': 'router'
|
'url_path': 'router'
|
||||||
}
|
}
|
||||||
|
|
||||||
assert panels.get('weather').as_dict() == {
|
assert panels.get('weather').to_response(self.hass, None) == {
|
||||||
'component_name': 'iframe',
|
'component_name': 'iframe',
|
||||||
'config': {'url': 'https://www.wunderground.com/us/ca/san-diego'},
|
'config': {'url': 'https://www.wunderground.com/us/ca/san-diego'},
|
||||||
'icon': 'mdi:weather',
|
'icon': 'mdi:weather',
|
||||||
'title': 'Weather',
|
'title': 'Weather',
|
||||||
'url': '/static/panels/ha-panel-iframe-md5md5.html',
|
'url': '/frontend_es5/panels/ha-panel-iframe-md5md5.html',
|
||||||
'url_path': 'weather',
|
'url_path': 'weather',
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ def test_get_panels(hass, websocket_client):
|
|||||||
"""Test get_panels command."""
|
"""Test get_panels command."""
|
||||||
yield from hass.components.frontend.async_register_built_in_panel(
|
yield from hass.components.frontend.async_register_built_in_panel(
|
||||||
'map', 'Map', 'mdi:account-location')
|
'map', 'Map', 'mdi:account-location')
|
||||||
|
hass.data[frontend.DATA_JS_VERSION] = 'es5'
|
||||||
websocket_client.send_json({
|
websocket_client.send_json({
|
||||||
'id': 5,
|
'id': 5,
|
||||||
'type': wapi.TYPE_GET_PANELS,
|
'type': wapi.TYPE_GET_PANELS,
|
||||||
@ -300,8 +300,14 @@ def test_get_panels(hass, websocket_client):
|
|||||||
assert msg['id'] == 5
|
assert msg['id'] == 5
|
||||||
assert msg['type'] == wapi.TYPE_RESULT
|
assert msg['type'] == wapi.TYPE_RESULT
|
||||||
assert msg['success']
|
assert msg['success']
|
||||||
assert msg['result'] == {url: panel.as_dict() for url, panel
|
assert msg['result'] == {'map': {
|
||||||
in hass.data[frontend.DATA_PANELS].items()}
|
'component_name': 'map',
|
||||||
|
'url_path': 'map',
|
||||||
|
'config': None,
|
||||||
|
'url': None,
|
||||||
|
'icon': 'mdi:account-location',
|
||||||
|
'title': 'Map',
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
|
Loading…
x
Reference in New Issue
Block a user