From febbe6d308f65e2289dd0ec40a1ec36570d8d541 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 11 Mar 2021 18:26:55 +0100 Subject: [PATCH 01/49] first draft of webpush pushkin --- sygnal/webpushpushkin.py | 166 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 sygnal/webpushpushkin.py diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py new file mode 100644 index 00000000..55b2dc03 --- /dev/null +++ b/sygnal/webpushpushkin.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import json +import logging +import time +from io import BytesIO +from json import JSONDecodeError + +from pywebpush import webpush, WebPushException +from py_vapid import Vapid +from opentracing import logs, tags +from prometheus_client import Counter, Gauge, Histogram +from twisted.enterprise.adbapi import ConnectionPool +from twisted.internet.defer import DeferredSemaphore +from twisted.web.client import FileBodyProducer, HTTPConnectionPool, readBody +from twisted.web.http_headers import Headers + +from sygnal.exceptions import ( + NotificationDispatchException, + TemporaryNotificationDispatchException, +) +from sygnal.helper.context_factory import ClientTLSOptionsFactory +from sygnal.helper.proxy.proxyagent_twisted import ProxyAgent +from sygnal.utils import NotificationLoggerAdapter, twisted_sleep + +from .exceptions import PushkinSetupException +from .notifications import ConcurrencyLimitedPushkin + +logger = logging.getLogger(__name__) + +MAX_TRIES = 3 +RETRY_DELAY_BASE = 10 +MAX_BYTES_PER_FIELD = 1024 + +DEFAULT_MAX_CONNECTIONS = 20 + + +class WebpushPushkin(ConcurrencyLimitedPushkin): + """ + Pushkin that relays notifications to Google/Firebase Cloud Messaging. + """ + + UNDERSTOOD_CONFIG_FIELDS = { + "type", + "max_connections", + "vapid_private_key", + "vapid_contact_email", + } | ConcurrencyLimitedPushkin.UNDERSTOOD_CONFIG_FIELDS + + def __init__(self, name, sygnal, config): + super(WebpushPushkin, self).__init__(name, sygnal, config) + + nonunderstood = set(self.cfg.keys()).difference(self.UNDERSTOOD_CONFIG_FIELDS) + if len(nonunderstood) > 0: + logger.warning( + "The following configuration fields are not understood: %s", + nonunderstood, + ) + + self.http_pool = HTTPConnectionPool(reactor=sygnal.reactor) + self.max_connections = self.get_config( + "max_connections", DEFAULT_MAX_CONNECTIONS + ) + self.connection_semaphore = DeferredSemaphore(self.max_connections) + self.http_pool.maxPersistentPerHost = self.max_connections + + tls_client_options_factory = ClientTLSOptionsFactory() + + # use the Sygnal global proxy configuration + proxy_url = sygnal.config.get("proxy") + + self.http_agent = ProxyAgent( + reactor=sygnal.reactor, + pool=self.http_pool, + contextFactory=tls_client_options_factory, + proxy_url_str=proxy_url, + ) + self.http_agent_wrapper = HttpAgentWrapper(self.http_agent) + + privkey_filename = self.get_config("vapid_private_key") + self.vapid_private_key = Vapid.from_file(private_key_file=self.get_config("vapid_private_key")) + self.vapid_contact_email = self.get_config("vapid_contact_email") + + logger.info("webpush pushkin created") + + async def _dispatch_notification_unlimited(self, n, device, context): + payload = { + 'room_name': n.room_name, + 'room_alias': n.room_alias, + 'prio': n.prio, + 'membership': n.membership, + 'sender_display_name': n.sender_display_name, + 'event_id': n.event_id, + 'room_id': n.room_id, + 'user_is_target': n.user_is_target, + 'type': n.type, + 'sender': n.sender, + } + data = json.dumps(payload) + p256dh = device.pushkey + endpoint = device.data["endpoint"] + auth = device.data["auth"] + subscription_info = { + 'endpoint': endpoint, + 'keys': { + 'p256dh': p256dh, + 'auth': auth + } + } + try: + response_wrapper = webpush( + subscription_info=subscription_info, + data=data, + vapid_private_key=self.vapid_private_key, + vapid_claims={"sub": "mailto:{}".format(self.vapid_contact_email)}, + requests_session=self.http_agent_wrapper + ) + response = await response_wrapper.deferred + response_text = (await readBody(response)).decode() + logger.info("webpush provider responded with status: %d, body: %s", response.code, response_text) + except Exception as exception: + raise TemporaryNotificationDispatchException( + "webpush request failure" + ) from exception + + return [] + +class HttpAgentWrapper: + def __init__(self, http_agent): + self.http_agent = http_agent + + def post(self, endpoint, data, headers, timeout): + logger.info("HttpAgentWrapper: POST %s", endpoint) + body_producer = FileBodyProducer(BytesIO(data)) + headers = { + b"User-Agent": ["sygnal"], + b"Content-Encoding": [headers["content-encoding"]], + b"Authorization": [headers["authorization"]], + b"TTL": [headers["ttl"]], + } + deferred = self.http_agent.request( + b"POST", + endpoint.encode(), + headers=Headers(headers), + bodyProducer=body_producer, + ) + return HttpResponseWrapper(deferred) + +class HttpResponseWrapper: + def __init__(self, deferred): + self.deferred = deferred + self.status_code = 0 + self.text = None + From 5aa5e961453cbc3bb96c249d9abe2f2359fd42b4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 11 Mar 2021 19:04:09 +0100 Subject: [PATCH 02/49] declare dependencies --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 05f5789a..aead512e 100755 --- a/setup.py +++ b/setup.py @@ -49,6 +49,8 @@ def read(fname): "idna>=2.8", "psycopg2>=2.8.4", "importlib_metadata", + "pywebpush@git+https://github.com/matrix-org/pywebpush" + "py-vapid>=1.7.0" ], long_description=read("README.rst"), ) From 2cba009697f023620183ec90866fbe30d2980cb1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 10:13:05 +0100 Subject: [PATCH 03/49] precompute the claims --- sygnal/webpushpushkin.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 55b2dc03..0eca708f 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -91,9 +91,8 @@ def __init__(self, name, sygnal, config): privkey_filename = self.get_config("vapid_private_key") self.vapid_private_key = Vapid.from_file(private_key_file=self.get_config("vapid_private_key")) - self.vapid_contact_email = self.get_config("vapid_contact_email") - - logger.info("webpush pushkin created") + vapid_contact_email = self.get_config("vapid_contact_email") + self.vapid_claims = {"sub": "mailto:{}".format(vapid_contact_email)} async def _dispatch_notification_unlimited(self, n, device, context): payload = { @@ -124,7 +123,7 @@ async def _dispatch_notification_unlimited(self, n, device, context): subscription_info=subscription_info, data=data, vapid_private_key=self.vapid_private_key, - vapid_claims={"sub": "mailto:{}".format(self.vapid_contact_email)}, + vapid_claims=self.vapid_claims, requests_session=self.http_agent_wrapper ) response = await response_wrapper.deferred From 657bacf093dd164f28658bee336c13fe62bb6f89 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 10:13:15 +0100 Subject: [PATCH 04/49] allow client to pass session_id for multiple pushers by 1 service worker A service worker can only have 1 push subscription, but for web clients that handle multiple matrix accounts like hydrogen, we want to be able to enable push for every account. We can do that by setting a pusher with the same web push details for each account, but we can't really tell which account a push notification is for, so forward a session_id set on the pusher data. --- sygnal/webpushpushkin.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 0eca708f..bc39a8ff 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -95,6 +95,17 @@ def __init__(self, name, sygnal, config): self.vapid_claims = {"sub": "mailto:{}".format(vapid_contact_email)} async def _dispatch_notification_unlimited(self, n, device, context): + p256dh = device.pushkey + endpoint = device.data["endpoint"] + session_id = device.data["session_id"] + auth = device.data["auth"] + subscription_info = { + 'endpoint': endpoint, + 'keys': { + 'p256dh': p256dh, + 'auth': auth + } + } payload = { 'room_name': n.room_name, 'room_alias': n.room_alias, @@ -106,18 +117,9 @@ async def _dispatch_notification_unlimited(self, n, device, context): 'user_is_target': n.user_is_target, 'type': n.type, 'sender': n.sender, + 'session_id': session_id, } data = json.dumps(payload) - p256dh = device.pushkey - endpoint = device.data["endpoint"] - auth = device.data["auth"] - subscription_info = { - 'endpoint': endpoint, - 'keys': { - 'p256dh': p256dh, - 'auth': auth - } - } try: response_wrapper = webpush( subscription_info=subscription_info, From 4dc8e601e745374147bb8b4b0ed358a016bde9fe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 14:59:38 +0100 Subject: [PATCH 05/49] apply style suggestion --- sygnal/webpushpushkin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index bc39a8ff..a82d76e8 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -62,8 +62,8 @@ class WebpushPushkin(ConcurrencyLimitedPushkin): def __init__(self, name, sygnal, config): super(WebpushPushkin, self).__init__(name, sygnal, config) - nonunderstood = set(self.cfg.keys()).difference(self.UNDERSTOOD_CONFIG_FIELDS) - if len(nonunderstood) > 0: + nonunderstood = self.cfg.keys() - self.UNDERSTOOD_CONFIG_FIELDS + if nonunderstood: logger.warning( "The following configuration fields are not understood: %s", nonunderstood, From dee1b1f5eab8347c2ba84b47396564a3ff915d56 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 15:02:41 +0100 Subject: [PATCH 06/49] point to commit hash for fork of pywebpush and add comment why needed --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aead512e..efbdc0e3 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,10 @@ def read(fname): "idna>=2.8", "psycopg2>=2.8.4", "importlib_metadata", - "pywebpush@git+https://github.com/matrix-org/pywebpush" + # a minor change was need to make this library work with async io, which has been proposed + # to merge upstream at https://github.com/web-push-libs/pywebpush/pull/133 + # for now, we point to the fork + "pywebpush@git+https://github.com/matrix-org/pywebpush#b723ef616eaf7cd79e592a9627d601b202017572" "py-vapid>=1.7.0" ], long_description=read("README.rst"), From b04db8fa8f7d09b10acd384beeedea2bbb179d39 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 15:04:46 +0100 Subject: [PATCH 07/49] english --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index efbdc0e3..16d25518 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def read(fname): "idna>=2.8", "psycopg2>=2.8.4", "importlib_metadata", - # a minor change was need to make this library work with async io, which has been proposed + # a minor change was needed to make this library work with async io, which has been proposed # to merge upstream at https://github.com/web-push-libs/pywebpush/pull/133 # for now, we point to the fork "pywebpush@git+https://github.com/matrix-org/pywebpush#b723ef616eaf7cd79e592a9627d601b202017572" From dc904cb21328bc6209074be3a63de3fa1f0be5ce Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Mar 2021 19:27:51 +0100 Subject: [PATCH 08/49] build payload more cautiously, and adopt default_payload like gcm/aspn --- sygnal/webpushpushkin.py | 69 ++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index a82d76e8..d47186ed 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -97,7 +97,6 @@ def __init__(self, name, sygnal, config): async def _dispatch_notification_unlimited(self, n, device, context): p256dh = device.pushkey endpoint = device.data["endpoint"] - session_id = device.data["session_id"] auth = device.data["auth"] subscription_info = { 'endpoint': endpoint, @@ -106,19 +105,7 @@ async def _dispatch_notification_unlimited(self, n, device, context): 'auth': auth } } - payload = { - 'room_name': n.room_name, - 'room_alias': n.room_alias, - 'prio': n.prio, - 'membership': n.membership, - 'sender_display_name': n.sender_display_name, - 'event_id': n.event_id, - 'room_id': n.room_id, - 'user_is_target': n.user_is_target, - 'type': n.type, - 'sender': n.sender, - 'session_id': session_id, - } + payload = WebpushPushkin._build_payload(n, device) data = json.dumps(payload) try: response_wrapper = webpush( @@ -130,13 +117,63 @@ async def _dispatch_notification_unlimited(self, n, device, context): ) response = await response_wrapper.deferred response_text = (await readBody(response)).decode() - logger.info("webpush provider responded with status: %d, body: %s", response.code, response_text) + except Exception as exception: raise TemporaryNotificationDispatchException( "webpush request failure" ) from exception - return [] + failed_pushkeys = [] + # assume 4xx is permanent and 5xx is temporary + if 400 <= response.code < 500: + failed_pushkeys.append(device.pushkey) + return failed_pushkeys + + @staticmethod + def _build_payload(n, device): + """ + Build the payload data to be sent. + Args: + n: Notification to build the payload for. + device (Device): Device information to which the constructed payload + will be sent. + + Returns: + JSON-compatible dict + """ + payload = {} + + if device.data: + payload.update(device.data.get("default_payload", {})) + + # if type is m.room.message, add content.msgtype and content.body + if getattr(n, "type", None) == "m.room.message" and getattr(n, "content", None): + content = n.content + for attr in ["msgtype", "body"]: + if getattr(content, attr, None): + payload[attr] = getattr(content, attr) + + for attr in [ + "room_id", + "room_name", + "room_alias", + "membership", + "sender", + "sender_display_name", + "event_id", + "user_is_target", + "type", + ]: + if getattr(n, attr, None): + payload[attr] = getattr(n, attr) + + if getattr(n, "counts", None): + counts = n.counts + for attr in ["unread", "missed_calls"]: + if getattr(counts, attr, None): + payload[attr] = getattr(counts, attr) + + return payload class HttpAgentWrapper: def __init__(self, http_agent): From 6d326e7aa20dfac02f4bcb8b0bb33f4b2eafcfef Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 18:20:39 +0100 Subject: [PATCH 09/49] always send content if present, as the payload is encrypted --- sygnal/webpushpushkin.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index d47186ed..bd4eab4b 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -146,23 +146,17 @@ def _build_payload(n, device): if device.data: payload.update(device.data.get("default_payload", {})) - # if type is m.room.message, add content.msgtype and content.body - if getattr(n, "type", None) == "m.room.message" and getattr(n, "content", None): - content = n.content - for attr in ["msgtype", "body"]: - if getattr(content, attr, None): - payload[attr] = getattr(content, attr) - for attr in [ "room_id", "room_name", "room_alias", "membership", + "event_id", "sender", "sender_display_name", - "event_id", "user_is_target", "type", + "content" ]: if getattr(n, attr, None): payload[attr] = getattr(n, attr) From 6f50a864967b8f8bcf8c7012d14d794d6b45c6a7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 18:21:08 +0100 Subject: [PATCH 10/49] always send the counts if they are set, even 0 --- sygnal/webpushpushkin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index bd4eab4b..a6ff241b 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -164,7 +164,7 @@ def _build_payload(n, device): if getattr(n, "counts", None): counts = n.counts for attr in ["unread", "missed_calls"]: - if getattr(counts, attr, None): + if getattr(counts, attr, None) != None: payload[attr] = getattr(counts, attr) return payload From f262ed207aa630f55f84450ffb9742a8c8422972 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 18:38:08 +0100 Subject: [PATCH 11/49] check config in ctor --- sygnal/webpushpushkin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index a6ff241b..53d7d372 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -15,9 +15,9 @@ import json import logging import time +import os.path; from io import BytesIO from json import JSONDecodeError - from pywebpush import webpush, WebPushException from py_vapid import Vapid from opentracing import logs, tags @@ -40,13 +40,8 @@ logger = logging.getLogger(__name__) -MAX_TRIES = 3 -RETRY_DELAY_BASE = 10 -MAX_BYTES_PER_FIELD = 1024 - DEFAULT_MAX_CONNECTIONS = 20 - class WebpushPushkin(ConcurrencyLimitedPushkin): """ Pushkin that relays notifications to Google/Firebase Cloud Messaging. @@ -90,8 +85,14 @@ def __init__(self, name, sygnal, config): self.http_agent_wrapper = HttpAgentWrapper(self.http_agent) privkey_filename = self.get_config("vapid_private_key") - self.vapid_private_key = Vapid.from_file(private_key_file=self.get_config("vapid_private_key")) + if not privkey_filename: + raise PushkinSetupException("'vapid_private_key' not set in config") + if not os.path.exists(privkey_filename): + raise PushkinSetupException("path in 'vapid_private_key' does not exist") + self.vapid_private_key = Vapid.from_file(private_key_file=privkey_filename) vapid_contact_email = self.get_config("vapid_contact_email") + if not vapid_contact_email: + raise PushkinSetupException("'vapid_contact_email' not set in config") self.vapid_claims = {"sub": "mailto:{}".format(vapid_contact_email)} async def _dispatch_notification_unlimited(self, n, device, context): From c4acf9de1a9b5504ca8a1726f053f581cd6e59cd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 18:45:54 +0100 Subject: [PATCH 12/49] update fork commit hash after PR feedback --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 16d25518..69b03b8e 100755 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ def read(fname): # a minor change was needed to make this library work with async io, which has been proposed # to merge upstream at https://github.com/web-push-libs/pywebpush/pull/133 # for now, we point to the fork - "pywebpush@git+https://github.com/matrix-org/pywebpush#b723ef616eaf7cd79e592a9627d601b202017572" + "pywebpush@git+https://github.com/matrix-org/pywebpush#f663c4633350600c4e70dd7c8c209cc1edaa6589" "py-vapid>=1.7.0" ], long_description=read("README.rst"), From 7ad9133ef68c88f91aba8ecae379c6cf77bfa524 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 19:20:25 +0100 Subject: [PATCH 13/49] copy GCM connection handling --- sygnal/webpushpushkin.py | 56 ++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 53d7d372..0c382d03 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -27,17 +27,28 @@ from twisted.web.client import FileBodyProducer, HTTPConnectionPool, readBody from twisted.web.http_headers import Headers -from sygnal.exceptions import ( - NotificationDispatchException, - TemporaryNotificationDispatchException, -) from sygnal.helper.context_factory import ClientTLSOptionsFactory from sygnal.helper.proxy.proxyagent_twisted import ProxyAgent -from sygnal.utils import NotificationLoggerAdapter, twisted_sleep from .exceptions import PushkinSetupException from .notifications import ConcurrencyLimitedPushkin +QUEUE_TIME_HISTOGRAM = Histogram( + "sygnal_webpush_queue_time", "Time taken waiting for a connection to webpush endpoint" +) + +SEND_TIME_HISTOGRAM = Histogram( + "sygnal_webpush_request_time", "Time taken to send HTTP request to webpush endpoint" +) + +PENDING_REQUESTS_GAUGE = Gauge( + "sygnal_pending_webpush_requests", "Number of webpush requests waiting for a connection" +) + +ACTIVE_REQUESTS_GAUGE = Gauge( + "sygnal_active_webpush_requests", "Number of webpush requests in flight" +) + logger = logging.getLogger(__name__) DEFAULT_MAX_CONNECTIONS = 20 @@ -108,21 +119,28 @@ async def _dispatch_notification_unlimited(self, n, device, context): } payload = WebpushPushkin._build_payload(n, device) data = json.dumps(payload) - try: - response_wrapper = webpush( - subscription_info=subscription_info, - data=data, - vapid_private_key=self.vapid_private_key, - vapid_claims=self.vapid_claims, - requests_session=self.http_agent_wrapper - ) - response = await response_wrapper.deferred - response_text = (await readBody(response)).decode() - except Exception as exception: - raise TemporaryNotificationDispatchException( - "webpush request failure" - ) from exception + # we use the semaphore to actually limit the number of concurrent + # requests, since the HTTPConnectionPool will actually just lead to more + # requests being created but not pooled – it does not perform limiting. + with QUEUE_TIME_HISTOGRAM.time(): + with PENDING_REQUESTS_GAUGE.track_inprogress(): + await self.connection_semaphore.acquire() + + try: + with SEND_TIME_HISTOGRAM.time(): + with ACTIVE_REQUESTS_GAUGE.track_inprogress(): + response_wrapper = webpush( + subscription_info=subscription_info, + data=data, + vapid_private_key=self.vapid_private_key, + vapid_claims=self.vapid_claims, + requests_session=self.http_agent_wrapper + ) + response = await response_wrapper.deferred + await readBody(response) + finally: + self.connection_semaphore.release() failed_pushkeys = [] # assume 4xx is permanent and 5xx is temporary From 0e233f2cd258f2fc9b0a098281eeccde89c2e087 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 19:39:52 +0100 Subject: [PATCH 14/49] add documentation for pusher --- docs/applications.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/applications.md b/docs/applications.md index 7441135b..06b4b7a0 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -203,3 +203,42 @@ within FCM's limit. Please also note that some fields will be unavailable if you registered a pusher with `event_id_only` format. +### Web Push + +#### Setup & configuration + +In the sygnal virtualenv, generate the server key pair by running `vapid --gen --applicationServerKey`. This will generate a `private_key.pem` (which you'll refer to in the config file with `vapid_private_key`) and `public_key.pem` file (which you should likely rename to a name that includes your application id), and a string labeled `Application Server Key`. You'll copy the Application Server Key to your web application to subscribe to the push manager: + +```js +pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: "...", +}); +``` + +You also need to set an e-mail address in `vapid_contact_email` in the config file, where the push gateway operator can reach you in case they need to notify you about your usage of their API. + +#### Push key and expected push data + +In your web application, the push manager subscribe method will return an object with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` property. The `p256dh` key is used as the push key, and the push data is expected `endpoint` and `auth`. You can also set `default_payload` in the push data; any properties set in it will be present in the push messages you receive, so it can be used to pass identifiers specific to your client (like which account the notification is for). + +Also note that because you can only have one push subscription per service worker, and hence per origin, you might create pushers for different accounts with the same p256dh push key. To prevent the server from removing other pushers with the same push key for your other users, you should set `append` to `true` when uploading your pusher. + +#### Notification format + +The notification as received by your web application will contain these keys if they were set by the homeserver, and otherwise omit them. + +``` +room_id +room_name +room_alias +membership +event_id +sender +sender_display_name +user_is_target +type +content +unread +missed_calls +``` \ No newline at end of file From 4325f15036147bb8300e5169f3357d681517a94f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 19:44:18 +0100 Subject: [PATCH 15/49] update docs --- docs/applications.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/applications.md b/docs/applications.md index 06b4b7a0..0e58bf1c 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -207,10 +207,10 @@ with `event_id_only` format. #### Setup & configuration -In the sygnal virtualenv, generate the server key pair by running `vapid --gen --applicationServerKey`. This will generate a `private_key.pem` (which you'll refer to in the config file with `vapid_private_key`) and `public_key.pem` file (which you should likely rename to a name that includes your application id), and a string labeled `Application Server Key`. You'll copy the Application Server Key to your web application to subscribe to the push manager: +In the sygnal virtualenv, generate the server key pair by running `vapid --gen --applicationServerKey`. This will generate a `private_key.pem` (which you'll refer to in the config file with `vapid_private_key`) and `public_key.pem` file, and also string labeled `Application Server Key`. You'll copy the Application Server Key to your web application to subscribe to the push manager: ```js -pushManager.subscribe({ +serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: "...", }); @@ -220,7 +220,7 @@ You also need to set an e-mail address in `vapid_contact_email` in the config fi #### Push key and expected push data -In your web application, the push manager subscribe method will return an object with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` property. The `p256dh` key is used as the push key, and the push data is expected `endpoint` and `auth`. You can also set `default_payload` in the push data; any properties set in it will be present in the push messages you receive, so it can be used to pass identifiers specific to your client (like which account the notification is for). +In your web application, [the push manager subscribe method](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) will return [a subscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` property. The `p256dh` key is used as the push key, and the push data is expected `endpoint` and `auth`. You can also set `default_payload` in the push data; any properties set in it will be present in the push messages you receive, so it can be used to pass identifiers specific to your client (like which account the notification is for). Also note that because you can only have one push subscription per service worker, and hence per origin, you might create pushers for different accounts with the same p256dh push key. To prevent the server from removing other pushers with the same push key for your other users, you should set `append` to `true` when uploading your pusher. From 34ab6b81eae0f79004e111d61585495cda1cddc9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 19:47:48 +0100 Subject: [PATCH 16/49] add changelog file --- changelog.d/177.doc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/177.doc diff --git a/changelog.d/177.doc b/changelog.d/177.doc new file mode 100644 index 00000000..d3f80b24 --- /dev/null +++ b/changelog.d/177.doc @@ -0,0 +1 @@ +Add webpush support \ No newline at end of file From cda7fdabf0568fe580caa381890a89596171913f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:12:04 +0100 Subject: [PATCH 17/49] fix code style in pushkin --- sygnal/webpushpushkin.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 0c382d03..30e9d1f9 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -14,15 +14,11 @@ # limitations under the License. import json import logging -import time -import os.path; +import os.path from io import BytesIO -from json import JSONDecodeError -from pywebpush import webpush, WebPushException +from pywebpush import webpush from py_vapid import Vapid -from opentracing import logs, tags -from prometheus_client import Counter, Gauge, Histogram -from twisted.enterprise.adbapi import ConnectionPool +from prometheus_client import Gauge, Histogram from twisted.internet.defer import DeferredSemaphore from twisted.web.client import FileBodyProducer, HTTPConnectionPool, readBody from twisted.web.http_headers import Headers @@ -34,25 +30,30 @@ from .notifications import ConcurrencyLimitedPushkin QUEUE_TIME_HISTOGRAM = Histogram( - "sygnal_webpush_queue_time", "Time taken waiting for a connection to webpush endpoint" + "sygnal_webpush_queue_time", + "Time taken waiting for a connection to webpush endpoint" ) SEND_TIME_HISTOGRAM = Histogram( - "sygnal_webpush_request_time", "Time taken to send HTTP request to webpush endpoint" + "sygnal_webpush_request_time", + "Time taken to send HTTP request to webpush endpoint" ) PENDING_REQUESTS_GAUGE = Gauge( - "sygnal_pending_webpush_requests", "Number of webpush requests waiting for a connection" + "sygnal_pending_webpush_requests", + "Number of webpush requests waiting for a connection" ) ACTIVE_REQUESTS_GAUGE = Gauge( - "sygnal_active_webpush_requests", "Number of webpush requests in flight" + "sygnal_active_webpush_requests", + "Number of webpush requests in flight" ) logger = logging.getLogger(__name__) DEFAULT_MAX_CONNECTIONS = 20 + class WebpushPushkin(ConcurrencyLimitedPushkin): """ Pushkin that relays notifications to Google/Firebase Cloud Messaging. @@ -183,11 +184,12 @@ def _build_payload(n, device): if getattr(n, "counts", None): counts = n.counts for attr in ["unread", "missed_calls"]: - if getattr(counts, attr, None) != None: + if getattr(counts, attr, None) is not None: payload[attr] = getattr(counts, attr) return payload + class HttpAgentWrapper: def __init__(self, http_agent): self.http_agent = http_agent @@ -209,9 +211,9 @@ def post(self, endpoint, data, headers, timeout): ) return HttpResponseWrapper(deferred) + class HttpResponseWrapper: def __init__(self, deferred): self.deferred = deferred self.status_code = 0 self.text = None - From 8002e5a4ecceac94e89210428b165af06b691558 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:12:14 +0100 Subject: [PATCH 18/49] fix setup.py code style errors --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 69b03b8e..02402513 100755 --- a/setup.py +++ b/setup.py @@ -49,10 +49,12 @@ def read(fname): "idna>=2.8", "psycopg2>=2.8.4", "importlib_metadata", - # a minor change was needed to make this library work with async io, which has been proposed - # to merge upstream at https://github.com/web-push-libs/pywebpush/pull/133 + # a minor change was needed to make this library work with async io, + # which has been proposed to merge upstream at + # https://github.com/web-push-libs/pywebpush/pull/133 # for now, we point to the fork - "pywebpush@git+https://github.com/matrix-org/pywebpush#f663c4633350600c4e70dd7c8c209cc1edaa6589" + "pywebpush@git+https://github.com/matrix-org/pywebpush" + + "#f663c4633350600c4e70dd7c8c209cc1edaa6589", "py-vapid>=1.7.0" ], long_description=read("README.rst"), From 35e0a0ed6b65e252b4e8e495e01b1d0b6dc0f746 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:23:55 +0100 Subject: [PATCH 19/49] wrong extension for changelog file --- changelog.d/{177.doc => 177.feature} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{177.doc => 177.feature} (100%) diff --git a/changelog.d/177.doc b/changelog.d/177.feature similarity index 100% rename from changelog.d/177.doc rename to changelog.d/177.feature From 538442c5e766174549b64d2da08a2e71508ae351 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:31:09 +0100 Subject: [PATCH 20/49] more codestyle fixes --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 02402513..91d6ea61 100755 --- a/setup.py +++ b/setup.py @@ -50,11 +50,11 @@ def read(fname): "psycopg2>=2.8.4", "importlib_metadata", # a minor change was needed to make this library work with async io, - # which has been proposed to merge upstream at + # which has been proposed to merge upstream at # https://github.com/web-push-libs/pywebpush/pull/133 # for now, we point to the fork "pywebpush@git+https://github.com/matrix-org/pywebpush" + - "#f663c4633350600c4e70dd7c8c209cc1edaa6589", + "#f663c4633350600c4e70dd7c8c209cc1edaa6589", "py-vapid>=1.7.0" ], long_description=read("README.rst"), From eba9c80da9f63965b021c2f1a929b3b7a48baa78 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:42:46 +0100 Subject: [PATCH 21/49] reformat to make linter happy --- setup.py | 6 +++--- sygnal/webpushpushkin.py | 21 ++++++++------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index 91d6ea61..528fe9d4 100755 --- a/setup.py +++ b/setup.py @@ -53,9 +53,9 @@ def read(fname): # which has been proposed to merge upstream at # https://github.com/web-push-libs/pywebpush/pull/133 # for now, we point to the fork - "pywebpush@git+https://github.com/matrix-org/pywebpush" + - "#f663c4633350600c4e70dd7c8c209cc1edaa6589", - "py-vapid>=1.7.0" + "pywebpush@git+https://github.com/matrix-org/pywebpush" + + "#f663c4633350600c4e70dd7c8c209cc1edaa6589", + "py-vapid>=1.7.0", ], long_description=read("README.rst"), ) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 30e9d1f9..b1bcd22a 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -31,22 +31,20 @@ QUEUE_TIME_HISTOGRAM = Histogram( "sygnal_webpush_queue_time", - "Time taken waiting for a connection to webpush endpoint" + "Time taken waiting for a connection to webpush endpoint", ) SEND_TIME_HISTOGRAM = Histogram( - "sygnal_webpush_request_time", - "Time taken to send HTTP request to webpush endpoint" + "sygnal_webpush_request_time", "Time taken to send HTTP request to webpush endpoint" ) PENDING_REQUESTS_GAUGE = Gauge( "sygnal_pending_webpush_requests", - "Number of webpush requests waiting for a connection" + "Number of webpush requests waiting for a connection", ) ACTIVE_REQUESTS_GAUGE = Gauge( - "sygnal_active_webpush_requests", - "Number of webpush requests in flight" + "sygnal_active_webpush_requests", "Number of webpush requests in flight" ) logger = logging.getLogger(__name__) @@ -112,11 +110,8 @@ async def _dispatch_notification_unlimited(self, n, device, context): endpoint = device.data["endpoint"] auth = device.data["auth"] subscription_info = { - 'endpoint': endpoint, - 'keys': { - 'p256dh': p256dh, - 'auth': auth - } + "endpoint": endpoint, + "keys": {"p256dh": p256dh, "auth": auth}, } payload = WebpushPushkin._build_payload(n, device) data = json.dumps(payload) @@ -136,7 +131,7 @@ async def _dispatch_notification_unlimited(self, n, device, context): data=data, vapid_private_key=self.vapid_private_key, vapid_claims=self.vapid_claims, - requests_session=self.http_agent_wrapper + requests_session=self.http_agent_wrapper, ) response = await response_wrapper.deferred await readBody(response) @@ -176,7 +171,7 @@ def _build_payload(n, device): "sender_display_name", "user_is_target", "type", - "content" + "content", ]: if getattr(n, attr, None): payload[attr] = getattr(n, attr) From 3198745a35540d4f13edcbf7f4121dfbb50c83c2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:45:14 +0100 Subject: [PATCH 22/49] import sorting --- sygnal/webpushpushkin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index b1bcd22a..08357263 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -16,9 +16,9 @@ import logging import os.path from io import BytesIO -from pywebpush import webpush -from py_vapid import Vapid from prometheus_client import Gauge, Histogram +from py_vapid import Vapid +from pywebpush import webpush from twisted.internet.defer import DeferredSemaphore from twisted.web.client import FileBodyProducer, HTTPConnectionPool, readBody from twisted.web.http_headers import Headers From f81c686aac9c116addc45799da641de1ebc5344c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 15 Mar 2021 20:47:23 +0100 Subject: [PATCH 23/49] =?UTF-8?q?the=20linter=20seems=20to=20want=20a=20ne?= =?UTF-8?q?wline=20here=20=C2=AF\=5F(=E3=83=84)=5F/=C2=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sygnal/webpushpushkin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 08357263..9652dc16 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -16,6 +16,7 @@ import logging import os.path from io import BytesIO + from prometheus_client import Gauge, Histogram from py_vapid import Vapid from pywebpush import webpush From 41158099b36e15d068fac34b6de1b94bae8b7056 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Mar 2021 11:40:25 +0100 Subject: [PATCH 24/49] upstream PR got merged, update git dependency --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 528fe9d4..e7d7b66b 100755 --- a/setup.py +++ b/setup.py @@ -50,11 +50,9 @@ def read(fname): "psycopg2>=2.8.4", "importlib_metadata", # a minor change was needed to make this library work with async io, - # which has been proposed to merge upstream at - # https://github.com/web-push-libs/pywebpush/pull/133 - # for now, we point to the fork - "pywebpush@git+https://github.com/matrix-org/pywebpush" - + "#f663c4633350600c4e70dd7c8c209cc1edaa6589", + # which has been been merged upstream but not released yet + "pywebpush@git+https://github.com/web-push-libs/pywebpush" + + "#336b1fb1edaab88c278b17995d29f9db55fe67b1", "py-vapid>=1.7.0", ], long_description=read("README.rst"), From 83def21ab0d1e95b6596d3a80a93ef4356f76568 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Mar 2021 12:33:05 +0100 Subject: [PATCH 25/49] ignore type checking for py_vapid and pywebpush --- mypy.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mypy.ini b/mypy.ini index 45ccfacc..18883366 100644 --- a/mypy.ini +++ b/mypy.ini @@ -43,3 +43,9 @@ ignore_missing_imports = True [mypy-OpenSSL.*] ignore_missing_imports = True + +[py_vapid.*] +ignore_missing_imports = True + +[pywebpush.*] +ignore_missing_imports = True From 8685f2c0d843d0b726106ff72e2b5266485c1be1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Mar 2021 14:16:54 +0100 Subject: [PATCH 26/49] 2nd attempt to ignore type checking for py_vapid and pywebpush --- mypy.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy.ini b/mypy.ini index 18883366..32aa4fb2 100644 --- a/mypy.ini +++ b/mypy.ini @@ -44,8 +44,8 @@ ignore_missing_imports = True [mypy-OpenSSL.*] ignore_missing_imports = True -[py_vapid.*] +[py_vapid] ignore_missing_imports = True -[pywebpush.*] +[pywebpush] ignore_missing_imports = True From ab653095f938f5108bb0ef7e6fd72c5e7bf10aee Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Mar 2021 14:24:24 +0100 Subject: [PATCH 27/49] 3rd attempt to ignore type checking for py_vapid and pywebpush --- mypy.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy.ini b/mypy.ini index 32aa4fb2..54aa196f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -44,8 +44,8 @@ ignore_missing_imports = True [mypy-OpenSSL.*] ignore_missing_imports = True -[py_vapid] +[mypy-py_vapid] ignore_missing_imports = True -[pywebpush] +[mypy-pywebpush] ignore_missing_imports = True From 8ded12f1c5c1dd9cc30e036771a4c9b9c0733f08 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 09:32:51 +0100 Subject: [PATCH 28/49] remove git dep, merged upstream PR got released --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index e7d7b66b..0e8dcab2 100755 --- a/setup.py +++ b/setup.py @@ -49,10 +49,7 @@ def read(fname): "idna>=2.8", "psycopg2>=2.8.4", "importlib_metadata", - # a minor change was needed to make this library work with async io, - # which has been been merged upstream but not released yet - "pywebpush@git+https://github.com/web-push-libs/pywebpush" - + "#336b1fb1edaab88c278b17995d29f9db55fe67b1", + "pywebpush>=1.13.0", "py-vapid>=1.7.0", ], long_description=read("README.rst"), From 9e3cad8bbbf3dfbe7c52ebf8a2a9ad997f36e3db Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 10:18:12 +0100 Subject: [PATCH 29/49] validate push data better, and reject pushkey if invalid --- sygnal/webpushpushkin.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 9652dc16..d410043f 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -108,8 +108,18 @@ def __init__(self, name, sygnal, config): async def _dispatch_notification_unlimited(self, n, device, context): p256dh = device.pushkey - endpoint = device.data["endpoint"] - auth = device.data["auth"] + if not isinstance(device.data, dict): + logger.info("device.data is not a dict, reject pushkey") + return [device.pushkey] + + endpoint = device.data.get("endpoint") + auth = device.data.get("auth") + + # required subscription info is missing, this is an invalid pusher + if not p256dh or not endpoint or not auth: + logger.info("subscription info missing, reject pushkey") + return [device.pushkey] + subscription_info = { "endpoint": endpoint, "keys": {"p256dh": p256dh, "auth": auth}, @@ -127,6 +137,7 @@ async def _dispatch_notification_unlimited(self, n, device, context): try: with SEND_TIME_HISTOGRAM.time(): with ACTIVE_REQUESTS_GAUGE.track_inprogress(): + logger.info("sending payload %s", data) response_wrapper = webpush( subscription_info=subscription_info, data=data, @@ -139,11 +150,10 @@ async def _dispatch_notification_unlimited(self, n, device, context): finally: self.connection_semaphore.release() - failed_pushkeys = [] # assume 4xx is permanent and 5xx is temporary if 400 <= response.code < 500: - failed_pushkeys.append(device.pushkey) - return failed_pushkeys + return [device.pushkey] + return [] @staticmethod def _build_payload(n, device): @@ -159,8 +169,9 @@ def _build_payload(n, device): """ payload = {} - if device.data: - payload.update(device.data.get("default_payload", {})) + default_payload = device.data.get("default_payload") + if isinstance(default_payload, dict): + payload.update(default_payload) for attr in [ "room_id", From 40f5a40c943f685f7f0657fb22aa70906e1a4d54 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 10:21:28 +0100 Subject: [PATCH 30/49] remove comment --- sygnal/webpushpushkin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index d410043f..b8154daf 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -115,7 +115,6 @@ async def _dispatch_notification_unlimited(self, n, device, context): endpoint = device.data.get("endpoint") auth = device.data.get("auth") - # required subscription info is missing, this is an invalid pusher if not p256dh or not endpoint or not auth: logger.info("subscription info missing, reject pushkey") return [device.pushkey] From 7531a295f785f6d1fb5b253013f4b06467d5cb29 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 13:44:33 +0000 Subject: [PATCH 31/49] Update sygnal/webpushpushkin.py Co-authored-by: Patrick Cloke --- sygnal/webpushpushkin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index b8154daf..fb18bfff 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -187,8 +187,8 @@ def _build_payload(n, device): if getattr(n, attr, None): payload[attr] = getattr(n, attr) - if getattr(n, "counts", None): - counts = n.counts + counts = getattr(n, "count", None) + if count is not None: for attr in ["unread", "missed_calls"]: if getattr(counts, attr, None) is not None: payload[attr] = getattr(counts, attr) From 74d72449e8abac3cf71123ca4244046346a2bdad Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 13:45:04 +0000 Subject: [PATCH 32/49] Update sygnal/webpushpushkin.py Co-authored-by: Patrick Cloke --- sygnal/webpushpushkin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index fb18bfff..617711d0 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -184,8 +184,9 @@ def _build_payload(n, device): "type", "content", ]: - if getattr(n, attr, None): - payload[attr] = getattr(n, attr) + value = getattr(n, attr, None) + if value: + payload[attr] = value counts = getattr(n, "count", None) if count is not None: From 299363f78f5a009a5759ee04acf8374216f958de Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 14:55:30 +0100 Subject: [PATCH 33/49] wrap any exception from loading vapid priv key at startup --- sygnal/webpushpushkin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 617711d0..d529b63d 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -100,7 +100,10 @@ def __init__(self, name, sygnal, config): raise PushkinSetupException("'vapid_private_key' not set in config") if not os.path.exists(privkey_filename): raise PushkinSetupException("path in 'vapid_private_key' does not exist") - self.vapid_private_key = Vapid.from_file(private_key_file=privkey_filename) + try: + self.vapid_private_key = Vapid.from_file(private_key_file=privkey_filename) + except BaseException as e: + raise PushkinSetupException("invalid 'vapid_private_key' file") from e vapid_contact_email = self.get_config("vapid_contact_email") if not vapid_contact_email: raise PushkinSetupException("'vapid_contact_email' not set in config") From 5d5cb381359a1ae0995f725a2c2f008745f6ff56 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:03:40 +0100 Subject: [PATCH 34/49] clean up docs --- docs/applications.md | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/applications.md b/docs/applications.md index 0e58bf1c..c8f5f144 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -207,7 +207,13 @@ with `event_id_only` format. #### Setup & configuration -In the sygnal virtualenv, generate the server key pair by running `vapid --gen --applicationServerKey`. This will generate a `private_key.pem` (which you'll refer to in the config file with `vapid_private_key`) and `public_key.pem` file, and also string labeled `Application Server Key`. You'll copy the Application Server Key to your web application to subscribe to the push manager: +In the sygnal virtualenv, generate the server key pair by running +`vapid --gen --applicationServerKey`. This will generate a `private_key.pem` +(which you'll refer to in the config file with `vapid_private_key`) +and `public_key.pem` file, and also string labeled `Application Server Key`. + +You'll copy the Application Server Key to your web application to subscribe +to the push manager: ```js serviceWorkerRegistration.pushManager.subscribe({ @@ -216,17 +222,35 @@ serviceWorkerRegistration.pushManager.subscribe({ }); ``` -You also need to set an e-mail address in `vapid_contact_email` in the config file, where the push gateway operator can reach you in case they need to notify you about your usage of their API. +You also need to set an e-mail address in `vapid_contact_email` in the config file, +where the push gateway operator can reach you in case they need to notify you +about your usage of their API. #### Push key and expected push data -In your web application, [the push manager subscribe method](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) will return [a subscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` property. The `p256dh` key is used as the push key, and the push data is expected `endpoint` and `auth`. You can also set `default_payload` in the push data; any properties set in it will be present in the push messages you receive, so it can be used to pass identifiers specific to your client (like which account the notification is for). - -Also note that because you can only have one push subscription per service worker, and hence per origin, you might create pushers for different accounts with the same p256dh push key. To prevent the server from removing other pushers with the same push key for your other users, you should set `append` to `true` when uploading your pusher. +In your web application, [the push manager subscribe method] +(https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) will return +[a subscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) +with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` +property. The `p256dh` key is used as the push key, and the push data is expected +`endpoint` and `auth`. You can also set `default_payload` in the push data; +any properties set in it will be present in the push messages you receive, +so it can be used to pass identifiers specific to your client +(like which account the notification is for). + +Also note that because you can only have one push subscription per service worker, +and hence per origin, you might create pushers for different accounts with the same +p256dh push key. To prevent the server from removing other pushers with the same +push key for your other users, you should set `append` to `true` when uploading +your pusher. #### Notification format -The notification as received by your web application will contain these keys if they were set by the homeserver, and otherwise omit them. +The notification as received by your web application will contain these keys +if they were set by the homeserver, and otherwise omit them. These are the +same as specified in [the push gateway spec] +(https://matrix.org/docs/spec/push_gateway/r0.1.0#post-matrix-push-v1-notify), +but `count` with `unread` and `missed_calls` is flattened into the notification object. ``` room_id From 10fe06150aebe4c6b21cb516b7eb77912e4dc522 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:07:01 +0100 Subject: [PATCH 35/49] counts, not count --- docs/applications.md | 2 +- sygnal/webpushpushkin.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/applications.md b/docs/applications.md index c8f5f144..ae8def77 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -250,7 +250,7 @@ The notification as received by your web application will contain these keys if they were set by the homeserver, and otherwise omit them. These are the same as specified in [the push gateway spec] (https://matrix.org/docs/spec/push_gateway/r0.1.0#post-matrix-push-v1-notify), -but `count` with `unread` and `missed_calls` is flattened into the notification object. +but `counts` with `unread` and `missed_calls` is flattened into the notification object. ``` room_id diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index d529b63d..a17a3107 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -191,11 +191,12 @@ def _build_payload(n, device): if value: payload[attr] = value - counts = getattr(n, "count", None) - if count is not None: + counts = getattr(n, "counts", None) + if counts is not None: for attr in ["unread", "missed_calls"]: - if getattr(counts, attr, None) is not None: - payload[attr] = getattr(counts, attr) + count_value = getattr(counts, attr, None) + if count_value is not None: + payload[attr] = count_value return payload From 0669242096c4c787cbb0563af766e8ed92fa764b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:11:51 +0100 Subject: [PATCH 36/49] clean up logging --- sygnal/webpushpushkin.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index a17a3107..614582c7 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -112,14 +112,22 @@ def __init__(self, name, sygnal, config): async def _dispatch_notification_unlimited(self, n, device, context): p256dh = device.pushkey if not isinstance(device.data, dict): - logger.info("device.data is not a dict, reject pushkey") + logger.warn( + "device.data is not a dict for pushkey %s, rejecting pushkey", p256dh + ) return [device.pushkey] endpoint = device.data.get("endpoint") auth = device.data.get("auth") if not p256dh or not endpoint or not auth: - logger.info("subscription info missing, reject pushkey") + logger.warn( + "subscription info incomplete " + + "(p256dh: %s, endpoint: %s, auth: %s), rejecting pushkey", + p256dh, + endpoint, + auth, + ) return [device.pushkey] subscription_info = { @@ -139,7 +147,6 @@ async def _dispatch_notification_unlimited(self, n, device, context): try: with SEND_TIME_HISTOGRAM.time(): with ACTIVE_REQUESTS_GAUGE.track_inprogress(): - logger.info("sending payload %s", data) response_wrapper = webpush( subscription_info=subscription_info, data=data, From 8f0303a6d1b0190decaed0c7266a55abaa132a27 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:23:04 +0100 Subject: [PATCH 37/49] provide docstrings for http wrapper --- sygnal/webpushpushkin.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 614582c7..e3cc3e65 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -207,7 +207,9 @@ def _build_payload(n, device): return payload - +""" +Provide a post method that matches the API expected from pywebpush. +""" class HttpAgentWrapper: def __init__(self, http_agent): self.http_agent = http_agent @@ -229,7 +231,23 @@ def post(self, endpoint, data, headers, timeout): ) return HttpResponseWrapper(deferred) - +""" +Provide a response object that matches the API expected from pywebpush. +pywebpush expects a synchronous api, while we use an asynchronous network api. + +To keep pywebpush happy we present it with some hardcoded values that +make its assertions pass while the async network call is happening +in the background. + +Attributes +---------- +deferred : Deferred + the deferred to await the actual response after calling pywebpush +status_code : int + defined to be 0 so pywebpush check if it's below 202 passes +test : str + set to None as pywebpush references this field for its logging +""" class HttpResponseWrapper: def __init__(self, deferred): self.deferred = deferred From 68876879f5834c4df1d667ec60d2ad11e05af23f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:23:41 +0100 Subject: [PATCH 38/49] status code 200 is probably more correct/future-proof here --- sygnal/webpushpushkin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index e3cc3e65..b6a632c7 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -244,12 +244,12 @@ def post(self, endpoint, data, headers, timeout): deferred : Deferred the deferred to await the actual response after calling pywebpush status_code : int - defined to be 0 so pywebpush check if it's below 202 passes + defined to be 200 so pywebpush check if it's below 202 passes test : str set to None as pywebpush references this field for its logging """ class HttpResponseWrapper: def __init__(self, deferred): self.deferred = deferred - self.status_code = 0 + self.status_code = 200 self.text = None From ae5e530067a2f3be66f315f7f2d39e72a3bb1cb9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:31:22 +0100 Subject: [PATCH 39/49] remove obsolete logging --- sygnal/webpushpushkin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index b6a632c7..f985e24a 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -215,7 +215,6 @@ def __init__(self, http_agent): self.http_agent = http_agent def post(self, endpoint, data, headers, timeout): - logger.info("HttpAgentWrapper: POST %s", endpoint) body_producer = FileBodyProducer(BytesIO(data)) headers = { b"User-Agent": ["sygnal"], From e28f78327ab82ab82b96586c2f629e8cb9a5ea41 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:31:29 +0100 Subject: [PATCH 40/49] add docstring for post method parameters --- sygnal/webpushpushkin.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index f985e24a..0364956f 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -215,6 +215,19 @@ def __init__(self, http_agent): self.http_agent = http_agent def post(self, endpoint, data, headers, timeout): + """ + Parameters + ---------- + endpoint: str + the full http url to post to + data: bytes + the (encrypted) binary body of the request + headers: py_vapid.CaseInsensitiveDict + a (costum) dictionary with the headers. + We convert these because http_agent requires the names in camel casing. + timeout: int + ignored for now + """ body_producer = FileBodyProducer(BytesIO(data)) headers = { b"User-Agent": ["sygnal"], From 0abb95da6c06dc28d4f8016d6fee5a4ae5603895 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:32:23 +0100 Subject: [PATCH 41/49] black-ify formatting --- sygnal/webpushpushkin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 0364956f..cab55726 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -207,9 +207,12 @@ def _build_payload(n, device): return payload + """ Provide a post method that matches the API expected from pywebpush. """ + + class HttpAgentWrapper: def __init__(self, http_agent): self.http_agent = http_agent @@ -243,6 +246,7 @@ def post(self, endpoint, data, headers, timeout): ) return HttpResponseWrapper(deferred) + """ Provide a response object that matches the API expected from pywebpush. pywebpush expects a synchronous api, while we use an asynchronous network api. @@ -260,6 +264,8 @@ def post(self, endpoint, data, headers, timeout): test : str set to None as pywebpush references this field for its logging """ + + class HttpResponseWrapper: def __init__(self, deferred): self.deferred = deferred From ec88640fc5d03ce5b3544d31f2e251df15fdf6e9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:40:42 +0100 Subject: [PATCH 42/49] remove trailing space --- sygnal/webpushpushkin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index cab55726..26dd9835 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -258,7 +258,7 @@ def post(self, endpoint, data, headers, timeout): Attributes ---------- deferred : Deferred - the deferred to await the actual response after calling pywebpush + the deferred to await the actual response after calling pywebpush status_code : int defined to be 200 so pywebpush check if it's below 202 passes test : str From 5403fca1489dd2a723c15e81e4e5a5a2beb7f5cf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Mar 2021 15:47:28 +0100 Subject: [PATCH 43/49] typo --- sygnal/webpushpushkin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 26dd9835..45bbde5f 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -261,7 +261,7 @@ def post(self, endpoint, data, headers, timeout): the deferred to await the actual response after calling pywebpush status_code : int defined to be 200 so pywebpush check if it's below 202 passes -test : str +text : str set to None as pywebpush references this field for its logging """ From ed7d038040ef32431909a205c77a241ec8a9423c Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 14:06:16 -0400 Subject: [PATCH 44/49] Re-work docstrings. --- sygnal/webpushpushkin.py | 73 ++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 45bbde5f..a175fb32 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -168,6 +168,7 @@ async def _dispatch_notification_unlimited(self, n, device, context): def _build_payload(n, device): """ Build the payload data to be sent. + Args: n: Notification to build the payload for. device (Device): Device information to which the constructed payload @@ -208,30 +209,29 @@ def _build_payload(n, device): return payload -""" -Provide a post method that matches the API expected from pywebpush. -""" - - class HttpAgentWrapper: + """ + Provide a post method that matches the API expected from pywebpush. + """ def __init__(self, http_agent): self.http_agent = http_agent def post(self, endpoint, data, headers, timeout): """ - Parameters - ---------- - endpoint: str - the full http url to post to - data: bytes - the (encrypted) binary body of the request - headers: py_vapid.CaseInsensitiveDict - a (costum) dictionary with the headers. - We convert these because http_agent requires the names in camel casing. - timeout: int - ignored for now + Convert the requests-like API to a Twisted API call. + + Args: + endpoint (str): + The full http url to post to + data (bytes): + the (encrypted) binary body of the request + headers (py_vapid.CaseInsensitiveDict): + A (costume) dictionary with the headers. + timeout (int) + Ignored for now """ body_producer = FileBodyProducer(BytesIO(data)) + # Convert the headers to the camelcase version. headers = { b"User-Agent": ["sygnal"], b"Content-Encoding": [headers["content-encoding"]], @@ -247,27 +247,26 @@ def post(self, endpoint, data, headers, timeout): return HttpResponseWrapper(deferred) -""" -Provide a response object that matches the API expected from pywebpush. -pywebpush expects a synchronous api, while we use an asynchronous network api. - -To keep pywebpush happy we present it with some hardcoded values that -make its assertions pass while the async network call is happening -in the background. - -Attributes ----------- -deferred : Deferred - the deferred to await the actual response after calling pywebpush -status_code : int - defined to be 200 so pywebpush check if it's below 202 passes -text : str - set to None as pywebpush references this field for its logging -""" - - class HttpResponseWrapper: + """ + Provide a response object that matches the API expected from pywebpush. + pywebpush expects a synchronous API, while we use an asynchronous API. + + To keep pywebpush happy we present it with some hardcoded values that + make its assertions pass while the async network call is happening + in the background. + + Attributes: + deferred (Deferred): + The deferred to await the actual response after calling pywebpush. + status_code (int): + Defined to be 200 so the pywebpush check to see if is below 202 + passes. + text (str): + Set to None as pywebpush references this field for its logging. + """ + status_code = 200 + text = None + def __init__(self, deferred): self.deferred = deferred - self.status_code = 200 - self.text = None From 65915db5090ccc7e17965c50dd99c715919d3f00 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 14:10:41 -0400 Subject: [PATCH 45/49] Update some of the documentation. --- docs/applications.md | 16 ++++++++-------- sygnal/webpushpushkin.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/applications.md b/docs/applications.md index ae8def77..ae38bebb 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -203,7 +203,7 @@ within FCM's limit. Please also note that some fields will be unavailable if you registered a pusher with `event_id_only` format. -### Web Push +### WebPush #### Setup & configuration @@ -228,8 +228,8 @@ about your usage of their API. #### Push key and expected push data -In your web application, [the push manager subscribe method] -(https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) will return +In your web application, [the push manager subscribe method](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) +will return [a subscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) with an `endpoint` and `keys` property, the latter containing a `p256dh` and `auth` property. The `p256dh` key is used as the push key, and the push data is expected @@ -246,11 +246,11 @@ your pusher. #### Notification format -The notification as received by your web application will contain these keys -if they were set by the homeserver, and otherwise omit them. These are the -same as specified in [the push gateway spec] -(https://matrix.org/docs/spec/push_gateway/r0.1.0#post-matrix-push-v1-notify), -but `counts` with `unread` and `missed_calls` is flattened into the notification object. +The notification as received by your web application will contain the following keys +(assuming they were sent by the homeserver). These are the +same as specified in [the push gateway spec](https://matrix.org/docs/spec/push_gateway/r0.1.0#post-matrix-push-v1-notify), +but the sub-keys of `counts` (`unread` and `missed_calls`) are flattened into +the notification object. ``` room_id diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index a175fb32..9e780ad9 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -32,20 +32,20 @@ QUEUE_TIME_HISTOGRAM = Histogram( "sygnal_webpush_queue_time", - "Time taken waiting for a connection to webpush endpoint", + "Time taken waiting for a connection to WebPush endpoint", ) SEND_TIME_HISTOGRAM = Histogram( - "sygnal_webpush_request_time", "Time taken to send HTTP request to webpush endpoint" + "sygnal_webpush_request_time", "Time taken to send HTTP request to WebPush endpoint" ) PENDING_REQUESTS_GAUGE = Gauge( "sygnal_pending_webpush_requests", - "Number of webpush requests waiting for a connection", + "Number of WebPush requests waiting for a connection", ) ACTIVE_REQUESTS_GAUGE = Gauge( - "sygnal_active_webpush_requests", "Number of webpush requests in flight" + "sygnal_active_webpush_requests", "Number of WebPush requests in flight" ) logger = logging.getLogger(__name__) From 4011ad31ada77b45c6b3c11d5c776966fd5b2edb Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 14:11:51 -0400 Subject: [PATCH 46/49] Update changelog. --- changelog.d/177.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/177.feature b/changelog.d/177.feature index d3f80b24..a5da3497 100644 --- a/changelog.d/177.feature +++ b/changelog.d/177.feature @@ -1 +1 @@ -Add webpush support \ No newline at end of file +Add experimental support for WebPush pushkins. \ No newline at end of file From ee432112cc54caab91595f70c2030487672f5c8f Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 14:15:17 -0400 Subject: [PATCH 47/49] Black-ify --- sygnal/webpushpushkin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index 9e780ad9..a7a5bc7a 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -213,6 +213,7 @@ class HttpAgentWrapper: """ Provide a post method that matches the API expected from pywebpush. """ + def __init__(self, http_agent): self.http_agent = http_agent @@ -265,6 +266,7 @@ class HttpResponseWrapper: text (str): Set to None as pywebpush references this field for its logging. """ + status_code = 200 text = None From 9ad59918f51a6b7e5fbdddc894658c3a74e69dc3 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 15:23:55 -0400 Subject: [PATCH 48/49] Clarify documentation. --- docs/applications.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/applications.md b/docs/applications.md index ae38bebb..25584925 100644 --- a/docs/applications.md +++ b/docs/applications.md @@ -247,7 +247,7 @@ your pusher. #### Notification format The notification as received by your web application will contain the following keys -(assuming they were sent by the homeserver). These are the +(assuming non-null values were sent by the homeserver). These are the same as specified in [the push gateway spec](https://matrix.org/docs/spec/push_gateway/r0.1.0#post-matrix-push-v1-notify), but the sub-keys of `counts` (`unread` and `missed_calls`) are flattened into the notification object. @@ -265,4 +265,4 @@ type content unread missed_calls -``` \ No newline at end of file +``` From 96cd23a3ef3a9e44ce661a4962b0339df3bb8cd4 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 17 Mar 2021 15:26:33 -0400 Subject: [PATCH 49/49] Catch a more specific exception. --- sygnal/webpushpushkin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sygnal/webpushpushkin.py b/sygnal/webpushpushkin.py index a7a5bc7a..6ed57d9a 100644 --- a/sygnal/webpushpushkin.py +++ b/sygnal/webpushpushkin.py @@ -18,7 +18,7 @@ from io import BytesIO from prometheus_client import Gauge, Histogram -from py_vapid import Vapid +from py_vapid import Vapid, VapidException from pywebpush import webpush from twisted.internet.defer import DeferredSemaphore from twisted.web.client import FileBodyProducer, HTTPConnectionPool, readBody @@ -102,7 +102,7 @@ def __init__(self, name, sygnal, config): raise PushkinSetupException("path in 'vapid_private_key' does not exist") try: self.vapid_private_key = Vapid.from_file(private_key_file=privkey_filename) - except BaseException as e: + except VapidException as e: raise PushkinSetupException("invalid 'vapid_private_key' file") from e vapid_contact_email = self.get_config("vapid_contact_email") if not vapid_contact_email: