diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py
index f70a9cb73c1..6a486bb6362 100644
--- a/homeassistant/components/notify/html5.py
+++ b/homeassistant/components/notify/html5.py
@@ -7,6 +7,7 @@ https://home-assistant.io/components/notify.html5/
import datetime
import json
import logging
+from functools import partial
import time
import uuid
@@ -20,7 +21,7 @@ from homeassistant.components.frontend import add_manifest_json_key
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.notify import (
ATTR_DATA, ATTR_TITLE, ATTR_TARGET, PLATFORM_SCHEMA, ATTR_TITLE_DEFAULT,
- BaseNotificationService)
+ BaseNotificationService, DOMAIN)
from homeassistant.const import (
URL_ROOT, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_INTERNAL_SERVER_ERROR)
from homeassistant.helpers import config_validation as cv
@@ -34,6 +35,8 @@ _LOGGER = logging.getLogger(__name__)
REGISTRATIONS_FILE = 'html5_push_registrations.conf'
+SERVICE_DISMISS = 'html5_dismiss'
+
ATTR_GCM_SENDER_ID = 'gcm_sender_id'
ATTR_GCM_API_KEY = 'gcm_api_key'
@@ -57,6 +60,7 @@ ATTR_ACTION = 'action'
ATTR_ACTIONS = 'actions'
ATTR_TYPE = 'type'
ATTR_URL = 'url'
+ATTR_DISMISS = 'dismiss'
ATTR_JWT = 'jwt'
@@ -80,6 +84,11 @@ SUBSCRIPTION_SCHEMA = vol.All(
})
)
+DISMISS_SERVICE_SCHEMA = vol.Schema({
+ vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]),
+ vol.Optional(ATTR_DATA): dict,
+})
+
REGISTER_SCHEMA = vol.Schema({
vol.Required(ATTR_SUBSCRIPTION): SUBSCRIPTION_SCHEMA,
vol.Required(ATTR_BROWSER): vol.In(['chrome', 'firefox']),
@@ -122,7 +131,8 @@ def get_service(hass, config, discovery_info=None):
add_manifest_json_key(
ATTR_GCM_SENDER_ID, config.get(ATTR_GCM_SENDER_ID))
- return HTML5NotificationService(gcm_api_key, registrations, json_path)
+ return HTML5NotificationService(
+ hass, gcm_api_key, registrations, json_path)
def _load_config(filename):
@@ -326,12 +336,29 @@ class HTML5PushCallbackView(HomeAssistantView):
class HTML5NotificationService(BaseNotificationService):
"""Implement the notification service for HTML5."""
- def __init__(self, gcm_key, registrations, json_path):
+ def __init__(self, hass, gcm_key, registrations, json_path):
"""Initialize the service."""
self._gcm_key = gcm_key
self.registrations = registrations
self.registrations_json_path = json_path
+ async def async_dismiss_message(service):
+ """Handle dismissing notification message service calls."""
+ kwargs = {}
+
+ if self.targets is not None:
+ kwargs[ATTR_TARGET] = self.targets
+ elif service.data.get(ATTR_TARGET) is not None:
+ kwargs[ATTR_TARGET] = service.data.get(ATTR_TARGET)
+
+ kwargs[ATTR_DATA] = service.data.get(ATTR_DATA)
+
+ await self.async_dismiss(**kwargs)
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_DISMISS, async_dismiss_message,
+ schema=DISMISS_SERVICE_SCHEMA)
+
@property
def targets(self):
"""Return a dictionary of registered targets."""
@@ -340,12 +367,28 @@ class HTML5NotificationService(BaseNotificationService):
targets[registration] = registration
return targets
+ def dismiss(self, **kwargs):
+ """Dismisses a notification."""
+ data = kwargs.get(ATTR_DATA)
+ tag = data.get(ATTR_TAG) if data else ""
+ payload = {
+ ATTR_TAG: tag,
+ ATTR_DISMISS: True,
+ ATTR_DATA: {}
+ }
+
+ self._push_message(payload, **kwargs)
+
+ async def async_dismiss(self, **kwargs):
+ """Dismisses a notification.
+
+ This method must be run in the event loop.
+ """
+ await self.hass.async_add_executor_job(
+ partial(self.dismiss, **kwargs))
+
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
- import jwt
- from pywebpush import WebPusher
-
- timestamp = int(time.time())
tag = str(uuid.uuid4())
payload = {
@@ -354,7 +397,6 @@ class HTML5NotificationService(BaseNotificationService):
ATTR_DATA: {},
'icon': '/static/icons/favicon-192x192.png',
ATTR_TAG: tag,
- 'timestamp': (timestamp*1000), # Javascript ms since epoch
ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
}
@@ -378,6 +420,17 @@ class HTML5NotificationService(BaseNotificationService):
payload.get(ATTR_ACTIONS) is None):
payload[ATTR_DATA][ATTR_URL] = URL_ROOT
+ self._push_message(payload, **kwargs)
+
+ def _push_message(self, payload, **kwargs):
+ """Send the message."""
+ import jwt
+ from pywebpush import WebPusher
+
+ timestamp = int(time.time())
+
+ payload['timestamp'] = (timestamp*1000) # Javascript ms since epoch
+
targets = kwargs.get(ATTR_TARGET)
if not targets:
diff --git a/homeassistant/components/notify/services.yaml b/homeassistant/components/notify/services.yaml
index 23b1c968c4a..1b7944cc7da 100644
--- a/homeassistant/components/notify/services.yaml
+++ b/homeassistant/components/notify/services.yaml
@@ -16,6 +16,16 @@ notify:
description: Extended information for notification. Optional depending on the platform.
example: platform specific
+html5_dismiss:
+ description: Dismiss a html5 notification.
+ fields:
+ target:
+ description: An array of targets. Optional.
+ example: ['my_phone', 'my_tablet']
+ data:
+ description: Extended information of notification. Supports tag. Optional.
+ example: '{ "tag": "tagname" }'
+
apns_register:
description: Registers a device to receive push notifications.
fields:
diff --git a/tests/components/notify/test_html5.py b/tests/components/notify/test_html5.py
index 6aeba650a8c..e33c297b166 100644
--- a/tests/components/notify/test_html5.py
+++ b/tests/components/notify/test_html5.py
@@ -81,6 +81,40 @@ class TestHtml5Notify:
assert service is not None
+ @patch('pywebpush.WebPusher')
+ def test_dismissing_message(self, mock_wp):
+ """Test dismissing message."""
+ hass = MagicMock()
+
+ data = {
+ 'device': SUBSCRIPTION_1
+ }
+
+ m = mock_open(read_data=json.dumps(data))
+ with patch(
+ 'homeassistant.util.json.open',
+ m, create=True
+ ):
+ service = html5.get_service(hass, {'gcm_sender_id': '100'})
+
+ assert service is not None
+
+ service.dismiss(target=['device', 'non_existing'],
+ data={'tag': 'test'})
+
+ assert len(mock_wp.mock_calls) == 3
+
+ # WebPusher constructor
+ assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1['subscription']
+ # Third mock_call checks the status_code of the response.
+ assert mock_wp.mock_calls[2][0] == '().send().status_code.__eq__'
+
+ # Call to send
+ payload = json.loads(mock_wp.mock_calls[1][1][0])
+
+ assert payload['dismiss'] is True
+ assert payload['tag'] == 'test'
+
@patch('pywebpush.WebPusher')
def test_sending_message(self, mock_wp):
"""Test sending message."""