pywebpush 2.1.2__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {pywebpush-2.1.2 → pywebpush-2.2.0}/CHANGELOG.md +14 -0
  2. {pywebpush-2.1.2 → pywebpush-2.2.0}/PKG-INFO +51 -15
  3. {pywebpush-2.1.2 → pywebpush-2.2.0}/README.md +49 -14
  4. {pywebpush-2.1.2 → pywebpush-2.2.0}/README.rst +99 -33
  5. {pywebpush-2.1.2 → pywebpush-2.2.0}/pyproject.toml +2 -1
  6. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush/__init__.py +6 -49
  7. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush/__main__.py +15 -0
  8. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush/tests/test_webpush.py +0 -30
  9. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/PKG-INFO +51 -15
  10. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/SOURCES.txt +0 -1
  11. pywebpush-2.1.2/PULL_REQUEST_TEMPLATE.md +0 -13
  12. {pywebpush-2.1.2 → pywebpush-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  13. {pywebpush-2.1.2 → pywebpush-2.2.0}/LICENSE +0 -0
  14. {pywebpush-2.1.2 → pywebpush-2.2.0}/MANIFEST.in +0 -0
  15. {pywebpush-2.1.2 → pywebpush-2.2.0}/entry_points.txt +0 -0
  16. {pywebpush-2.1.2 → pywebpush-2.2.0}/local_test.txt +0 -0
  17. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush/foo.py +0 -0
  18. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush/tests/__init__.py +0 -0
  19. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/dependency_links.txt +0 -0
  20. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/entry_points.txt +0 -0
  21. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/requires.txt +0 -0
  22. {pywebpush-2.1.2 → pywebpush-2.2.0}/pywebpush.egg-info/top_level.txt +0 -0
  23. {pywebpush-2.1.2 → pywebpush-2.2.0}/requirements.txt +0 -0
  24. {pywebpush-2.1.2 → pywebpush-2.2.0}/setup.cfg +0 -0
  25. {pywebpush-2.1.2 → pywebpush-2.2.0}/test-requirements.txt +0 -0
@@ -1,6 +1,20 @@
1
1
  # I am terrible at keeping this up-to-date.
2
2
 
3
+ ## 2.2.0 (2026-)
4
+
5
+ - Update `rst` files to reflect `md` file changes
6
+
7
+ Add comment discussing additional work for Windows Notification Service (WNS)
8
+
9
+ - Update the README.md file to mention the required, non-standard headers.
10
+
11
+ _BREAKING_CHANGE_
12
+ This version also drops legacy support for GCM/FCM authorization keys, since those items
13
+ are obsolete according to Google.
14
+ See https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-legacy-protocol-send-requests
15
+
3
16
  ## 2.1.2 (2025-11-10)
17
+
4
18
  chore: fix formatting
5
19
  chore: update CHANGELOG
6
20
  chore: note admin update
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebpush
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: WebPush publication library
5
5
  Author-email: JR Conlin <src+webpusher@jrconlin.com>
6
6
  License: MPL-2.0
@@ -10,6 +10,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
10
10
  Classifier: Programming Language :: Python :: Implementation :: PyPy
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
13
14
  Description-Content-Type: text/markdown
14
15
  License-File: LICENSE
15
16
  Requires-Dist: aiohttp
@@ -26,9 +27,6 @@ Dynamic: license-file
26
27
 
27
28
  # Webpush Data encryption library for Python
28
29
 
29
- [![Build Status](https://travis-ci.org/web-push-libs/pywebpush.svg?branch=main)](https://travis-ci.org/web-push-libs/pywebpush)
30
- [![Requirements Status](https://requires.io/github/web-push-libs/pywebpush/requirements.svg?branch=main)](https://requires.io/github/web-push-libs/pywebpush/requirements/?branch=main)
31
-
32
30
  This library is available on [pypi as pywebpush](https://pypi.python.org/pypi/pywebpush).
33
31
  Source is available on [github](https://github.com/mozilla-services/pywebpush).
34
32
  Please note: This library was designated as a `Critical Project` by PyPi, it is currently
@@ -64,6 +62,11 @@ referenced to the user who requested it, and recall it when there's
64
62
  a new push subscription update is left as an exercise for the
65
63
  reader.
66
64
 
65
+ _*Note:*_ Some platforms (like Microsoft Windows) require additional
66
+ headers specified for the Push call. These additional headers are not
67
+ standard and may be rejected by other Push services. Please see
68
+ [Special Instructions](#special-instructions) below.
69
+
67
70
  ### Sending Data using `webpush()` One Call
68
71
 
69
72
  In many cases, your code will be sending a single message to many
@@ -82,7 +85,7 @@ webpush(subscription_info,
82
85
  This will encode `data`, add the appropriate VAPID auth headers if required and send it to the push server identified
83
86
  in the `subscription_info` block.
84
87
 
85
- ##### Parameters
88
+ #### Parameters
86
89
 
87
90
  _subscription_info_ - The `dict` of the subscription info (described above).
88
91
 
@@ -107,7 +110,7 @@ e.g. the output of:
107
110
  openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
108
111
  ```
109
112
 
110
- ##### Example
113
+ #### Example
111
114
 
112
115
  ```python
113
116
  from pywebpush import webpush, WebPushException
@@ -145,7 +148,7 @@ can pass just `wp = WebPusher(subscription_info)`. This will return a `WebPusher
145
148
 
146
149
  The following methods are available:
147
150
 
148
- #### `.send(data, headers={}, ttl=0, gcm_key="", reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
151
+ #### `.send(data, headers={}, ttl=0, reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
149
152
 
150
153
  Send the data using additional parameters. On error, returns a `WebPushException`
151
154
 
@@ -157,9 +160,6 @@ _headers_ A `dict` containing any additional headers to send
157
160
 
158
161
  _ttl_ Message Time To Live on Push Server waiting for the client to reconnect (in seconds)
159
162
 
160
- _gcm_key_ Google Cloud Messaging key (if using the older GCM push system) This is the API key obtained from the Google
161
- Developer Console.
162
-
163
163
  _reg_id_ Google Cloud Messaging registration ID (will be extracted from endpoint if not specified)
164
164
 
165
165
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
@@ -172,10 +172,10 @@ See [requests documentation](http://docs.python-requests.org/en/master/user/quic
172
172
 
173
173
  ##### Example
174
174
 
175
- to send from Chrome using the old GCM mode:
175
+ to send to a user on Chrome:
176
176
 
177
177
  ```python
178
- WebPusher(subscription_info).send(data, headers, ttl, gcm_key)
178
+ WebPusher(subscription_info).send(data, headers, ttl)
179
179
  ```
180
180
 
181
181
  #### `.encode(data, content_encoding="aes128gcm")`
@@ -188,7 +188,7 @@ _data_ Binary string of data to send
188
188
 
189
189
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
190
190
 
191
- *Note* This will return a `NoData` exception if the data is not present or empty. It is completely
191
+ _*Note*_ This will return a `NoData` exception if the data is not present or empty. It is completely
192
192
  valid to send a WebPush notification with no data, but encoding is a no-op in that case. Best not
193
193
  to call it if you don't have data.
194
194
 
@@ -200,8 +200,7 @@ encoded_data = WebPush(subscription_info).encode(data)
200
200
 
201
201
  ## Stand Alone Webpush
202
202
 
203
- If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the
204
- ./bin directory.
203
+ If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the `./bin` directory.
205
204
 
206
205
  This uses two files:
207
206
 
@@ -227,3 +226,40 @@ If you're interested in just testing your applications WebPush interface, you co
227
226
  which will encrypt and send the contents of `stuff_to_send.data`.
228
227
 
229
228
  See `./bin/pywebpush --help` for available commands and options.
229
+
230
+ ## Special Instructions
231
+
232
+ ### Windows
233
+
234
+ [Microsoft requires](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/push-request-response-headers#request-parameters)
235
+ one extra header and suggests several additional headers. Users have reported that not including
236
+ these headers can cause notifications to fail to appear. These additional headers are non-standard
237
+ and may be rejected by other platforms. You should be cautious including them with calls to
238
+ non-Microsoft platforms, or to non-Microsoft destinations.
239
+
240
+ As of 2024-Apr-19, Microsoft requires an `X-WNS-Type` header. As an example, you can include this
241
+ header within the command line call:
242
+
243
+ Content of the `windows_headers.json` file:
244
+
245
+ ```json
246
+ {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}
247
+ ```
248
+
249
+ _*Note*_ : This includes both the `TTL` set to 10 minutes and the required matching `Content-Type` header for `wns/toast`.
250
+
251
+ ```bash
252
+ pywebpush --data stuff_to_send.xml \
253
+ --info edge_user_info.json \
254
+ --head windows_headers.json \
255
+ --claims vapid_claims.json
256
+ ```
257
+
258
+ ### Google Cloud Messaging (GCM)
259
+
260
+ Please note that GCM has been sunset by Google. Providers should
261
+ use [Firebase Cloud Messaging](https://firebase.google.com/support/troubleshooter/fcm/tokens/gcm) instead.
262
+
263
+ Please also note that sending messages directly to FCM is not supported by this library. In the past, you could use an insecure `gcm_key` as a proxy for the authentication service. [This was disabled in June 2024](https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-legacy-protocol-send-requests).
264
+
265
+ Sending WebPush messages to Google Chrome users should not be impacted by this change.
@@ -1,8 +1,5 @@
1
1
  # Webpush Data encryption library for Python
2
2
 
3
- [![Build Status](https://travis-ci.org/web-push-libs/pywebpush.svg?branch=main)](https://travis-ci.org/web-push-libs/pywebpush)
4
- [![Requirements Status](https://requires.io/github/web-push-libs/pywebpush/requirements.svg?branch=main)](https://requires.io/github/web-push-libs/pywebpush/requirements/?branch=main)
5
-
6
3
  This library is available on [pypi as pywebpush](https://pypi.python.org/pypi/pywebpush).
7
4
  Source is available on [github](https://github.com/mozilla-services/pywebpush).
8
5
  Please note: This library was designated as a `Critical Project` by PyPi, it is currently
@@ -38,6 +35,11 @@ referenced to the user who requested it, and recall it when there's
38
35
  a new push subscription update is left as an exercise for the
39
36
  reader.
40
37
 
38
+ _*Note:*_ Some platforms (like Microsoft Windows) require additional
39
+ headers specified for the Push call. These additional headers are not
40
+ standard and may be rejected by other Push services. Please see
41
+ [Special Instructions](#special-instructions) below.
42
+
41
43
  ### Sending Data using `webpush()` One Call
42
44
 
43
45
  In many cases, your code will be sending a single message to many
@@ -56,7 +58,7 @@ webpush(subscription_info,
56
58
  This will encode `data`, add the appropriate VAPID auth headers if required and send it to the push server identified
57
59
  in the `subscription_info` block.
58
60
 
59
- ##### Parameters
61
+ #### Parameters
60
62
 
61
63
  _subscription_info_ - The `dict` of the subscription info (described above).
62
64
 
@@ -81,7 +83,7 @@ e.g. the output of:
81
83
  openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
82
84
  ```
83
85
 
84
- ##### Example
86
+ #### Example
85
87
 
86
88
  ```python
87
89
  from pywebpush import webpush, WebPushException
@@ -119,7 +121,7 @@ can pass just `wp = WebPusher(subscription_info)`. This will return a `WebPusher
119
121
 
120
122
  The following methods are available:
121
123
 
122
- #### `.send(data, headers={}, ttl=0, gcm_key="", reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
124
+ #### `.send(data, headers={}, ttl=0, reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
123
125
 
124
126
  Send the data using additional parameters. On error, returns a `WebPushException`
125
127
 
@@ -131,9 +133,6 @@ _headers_ A `dict` containing any additional headers to send
131
133
 
132
134
  _ttl_ Message Time To Live on Push Server waiting for the client to reconnect (in seconds)
133
135
 
134
- _gcm_key_ Google Cloud Messaging key (if using the older GCM push system) This is the API key obtained from the Google
135
- Developer Console.
136
-
137
136
  _reg_id_ Google Cloud Messaging registration ID (will be extracted from endpoint if not specified)
138
137
 
139
138
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
@@ -146,10 +145,10 @@ See [requests documentation](http://docs.python-requests.org/en/master/user/quic
146
145
 
147
146
  ##### Example
148
147
 
149
- to send from Chrome using the old GCM mode:
148
+ to send to a user on Chrome:
150
149
 
151
150
  ```python
152
- WebPusher(subscription_info).send(data, headers, ttl, gcm_key)
151
+ WebPusher(subscription_info).send(data, headers, ttl)
153
152
  ```
154
153
 
155
154
  #### `.encode(data, content_encoding="aes128gcm")`
@@ -162,7 +161,7 @@ _data_ Binary string of data to send
162
161
 
163
162
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
164
163
 
165
- *Note* This will return a `NoData` exception if the data is not present or empty. It is completely
164
+ _*Note*_ This will return a `NoData` exception if the data is not present or empty. It is completely
166
165
  valid to send a WebPush notification with no data, but encoding is a no-op in that case. Best not
167
166
  to call it if you don't have data.
168
167
 
@@ -174,8 +173,7 @@ encoded_data = WebPush(subscription_info).encode(data)
174
173
 
175
174
  ## Stand Alone Webpush
176
175
 
177
- If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the
178
- ./bin directory.
176
+ If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the `./bin` directory.
179
177
 
180
178
  This uses two files:
181
179
 
@@ -201,3 +199,40 @@ If you're interested in just testing your applications WebPush interface, you co
201
199
  which will encrypt and send the contents of `stuff_to_send.data`.
202
200
 
203
201
  See `./bin/pywebpush --help` for available commands and options.
202
+
203
+ ## Special Instructions
204
+
205
+ ### Windows
206
+
207
+ [Microsoft requires](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/push-request-response-headers#request-parameters)
208
+ one extra header and suggests several additional headers. Users have reported that not including
209
+ these headers can cause notifications to fail to appear. These additional headers are non-standard
210
+ and may be rejected by other platforms. You should be cautious including them with calls to
211
+ non-Microsoft platforms, or to non-Microsoft destinations.
212
+
213
+ As of 2024-Apr-19, Microsoft requires an `X-WNS-Type` header. As an example, you can include this
214
+ header within the command line call:
215
+
216
+ Content of the `windows_headers.json` file:
217
+
218
+ ```json
219
+ {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}
220
+ ```
221
+
222
+ _*Note*_ : This includes both the `TTL` set to 10 minutes and the required matching `Content-Type` header for `wns/toast`.
223
+
224
+ ```bash
225
+ pywebpush --data stuff_to_send.xml \
226
+ --info edge_user_info.json \
227
+ --head windows_headers.json \
228
+ --claims vapid_claims.json
229
+ ```
230
+
231
+ ### Google Cloud Messaging (GCM)
232
+
233
+ Please note that GCM has been sunset by Google. Providers should
234
+ use [Firebase Cloud Messaging](https://firebase.google.com/support/troubleshooter/fcm/tokens/gcm) instead.
235
+
236
+ Please also note that sending messages directly to FCM is not supported by this library. In the past, you could use an insecure `gcm_key` as a proxy for the authentication service. [This was disabled in June 2024](https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-legacy-protocol-send-requests).
237
+
238
+ Sending WebPush messages to Google Chrome users should not be impacted by this change.
@@ -1,8 +1,6 @@
1
1
  Webpush Data encryption library for Python
2
2
  ==========================================
3
3
 
4
- |Build Status| |Requirements Status|
5
-
6
4
  This library is available on `pypi as
7
5
  pywebpush <https://pypi.python.org/pypi/pywebpush>`__. Source is
8
6
  available on `github <https://github.com/mozilla-services/pywebpush>`__.
@@ -14,12 +12,8 @@ make of that what you will.
14
12
  Installation
15
13
  ------------
16
14
 
17
- You’ll need to run ``python -m venv venv``. Then
18
-
19
- .. code:: bash
20
-
21
- venv/bin/pip install -r requirements.txt
22
- venv/bin/python -m pip install -e .
15
+ To work with this repo locally, you’ll need to run
16
+ ``python -m venv venv``. Then ``venv/bin/pip install --editable .``
23
17
 
24
18
  Usage
25
19
  -----
@@ -35,12 +29,20 @@ As illustration, a ``subscription_info`` object may look like:
35
29
 
36
30
  .. code:: json
37
31
 
38
- {"endpoint": "https://updates.push.services.mozilla.com/push/v1/gAA...", "keys": {"auth": "k8J...", "p256dh": "BOr..."}}
32
+ {
33
+ "endpoint": "https://updates.push.services.mozilla.com/push/v1/gAA...",
34
+ "keys": { "auth": "k8J...", "p256dh": "BOr..." }
35
+ }
39
36
 
40
37
  How you send the PushSubscription data to your backend, store it
41
38
  referenced to the user who requested it, and recall it when there’s a
42
39
  new push subscription update is left as an exercise for the reader.
43
40
 
41
+ *Note:* Some platforms (like Microsoft Windows) require additional
42
+ headers specified for the Push call. These additional headers are not
43
+ standard and may be rejected by other Push services. Please see `Special
44
+ Instructions <#special-instructions>`__ below.
45
+
44
46
  Sending Data using ``webpush()`` One Call
45
47
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46
48
 
@@ -60,7 +62,8 @@ This will encode ``data``, add the appropriate VAPID auth headers if
60
62
  required and send it to the push server identified in the
61
63
  ``subscription_info`` block.
62
64
 
63
- **Parameters**
65
+ Parameters
66
+ ^^^^^^^^^^
64
67
 
65
68
  *subscription_info* - The ``dict`` of the subscription info (described
66
69
  above).
@@ -78,7 +81,9 @@ standard form.
78
81
  authorization (See
79
82
  `py_vapid <https://github.com/web-push-libs/vapid/tree/master/python>`__
80
83
  for more details). If ``aud`` is not specified, pywebpush will attempt
81
- to auto-fill from the ``endpoint``.
84
+ to auto-fill from the ``endpoint``. If ``exp`` is not specified or set
85
+ in the past, it will be set to 12 hours from now. In both cases, the
86
+ passed ``dict`` **will be mutated** after the call.
82
87
 
83
88
  *vapid_private_key* - Either a path to a VAPID EC2 private key PEM file,
84
89
  or a string containing the DER representation. (See
@@ -93,7 +98,8 @@ e.g. the output of:
93
98
 
94
99
  openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
95
100
 
96
- **Example**
101
+ Example
102
+ ^^^^^^^
97
103
 
98
104
  .. code:: python
99
105
 
@@ -134,13 +140,16 @@ object.
134
140
 
135
141
  The following methods are available:
136
142
 
137
- ``.send(data, headers={}, ttl=0, gcm_key="", reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)``
138
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
143
+ ``.send(data, headers={}, ttl=0, reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)``
144
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
139
145
 
140
146
  Send the data using additional parameters. On error, returns a
141
147
  ``WebPushException``
142
148
 
143
- **Parameters**
149
+ .. _parameters-1:
150
+
151
+ Parameters
152
+ ''''''''''
144
153
 
145
154
  *data* Binary string of data to send
146
155
 
@@ -149,9 +158,6 @@ Send the data using additional parameters. On error, returns a
149
158
  *ttl* Message Time To Live on Push Server waiting for the client to
150
159
  reconnect (in seconds)
151
160
 
152
- *gcm_key* Google Cloud Messaging key (if using the older GCM push
153
- system) This is the API key obtained from the Google Developer Console.
154
-
155
161
  *reg_id* Google Cloud Messaging registration ID (will be extracted from
156
162
  endpoint if not specified)
157
163
 
@@ -165,13 +171,16 @@ purposes.
165
171
  *timeout* timeout for requests POST query. See `requests
166
172
  documentation <http://docs.python-requests.org/en/master/user/quickstart/#timeouts>`__.
167
173
 
168
- **Example**
174
+ .. _example-1:
169
175
 
170
- to send from Chrome using the old GCM mode:
176
+ Example
177
+ '''''''
178
+
179
+ to send to a user on Chrome:
171
180
 
172
181
  .. code:: python
173
182
 
174
- WebPusher(subscription_info).send(data, headers, ttl, gcm_key)
183
+ WebPusher(subscription_info).send(data, headers, ttl)
175
184
 
176
185
  ``.encode(data, content_encoding="aes128gcm")``
177
186
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -179,13 +188,24 @@ to send from Chrome using the old GCM mode:
179
188
  Encode the ``data`` for future use. On error, returns a
180
189
  ``WebPushException``
181
190
 
182
- **Parameters**
191
+ .. _parameters-2:
192
+
193
+ Parameters
194
+ ''''''''''
183
195
 
184
196
  *data* Binary string of data to send
185
197
 
186
198
  *content_encoding* ECE content encoding type (defaults to “aes128gcm”)
187
199
 
188
- **Example**
200
+ *Note* This will return a ``NoData`` exception if the data is not
201
+ present or empty. It is completely valid to send a WebPush notification
202
+ with no data, but encoding is a no-op in that case. Best not to call it
203
+ if you don’t have data.
204
+
205
+ .. _example-2:
206
+
207
+ Example
208
+ '''''''
189
209
 
190
210
  .. code:: python
191
211
 
@@ -195,7 +215,7 @@ Stand Alone Webpush
195
215
  -------------------
196
216
 
197
217
  If you’re not really into coding your own solution, there’s also a
198
- “stand-alone” ``pywebpush`` command in the ./bin directory.
218
+ “stand-alone” ``pywebpush`` command in the ``./bin`` directory.
199
219
 
200
220
  This uses two files:
201
221
 
@@ -207,11 +227,13 @@ This uses two files:
207
227
 
208
228
  .. code:: json
209
229
 
210
- {"endpoint": "https://push...",
211
- "keys": {
212
- "auth": "ab01...",
213
- "p256dh": "aa02..."
214
- }}
230
+ {
231
+ "endpoint": "https://push...",
232
+ "keys": {
233
+ "auth": "ab01...",
234
+ "p256dh": "aa02..."
235
+ }
236
+ }
215
237
 
216
238
  If you’re interested in just testing your applications WebPush
217
239
  interface, you could use the Command Line:
@@ -224,7 +246,51 @@ which will encrypt and send the contents of ``stuff_to_send.data``.
224
246
 
225
247
  See ``./bin/pywebpush --help`` for available commands and options.
226
248
 
227
- .. |Build Status| image:: https://travis-ci.org/web-push-libs/pywebpush.svg?branch=main
228
- :target: https://travis-ci.org/web-push-libs/pywebpush
229
- .. |Requirements Status| image:: https://requires.io/github/web-push-libs/pywebpush/requirements.svg?branch=main
230
- :target: https://requires.io/github/web-push-libs/pywebpush/requirements/?branch=main
249
+ Special Instructions
250
+ --------------------
251
+
252
+ Windows
253
+ ~~~~~~~
254
+
255
+ `Microsoft
256
+ requires <https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/push-request-response-headers#request-parameters>`__
257
+ one extra header and suggests several additional headers. Users have
258
+ reported that not including these headers can cause notifications to
259
+ fail to appear. These additional headers are non-standard and may be
260
+ rejected by other platforms. You should be cautious including them with
261
+ calls to non-Microsoft platforms, or to non-Microsoft destinations.
262
+
263
+ As of 2024-Apr-19, Microsoft requires an ``X-WNS-Type`` header. As an
264
+ example, you can include this header within the command line call:
265
+
266
+ Content of the ``windows_headers.json`` file:
267
+
268
+ .. code:: json
269
+
270
+ {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}
271
+
272
+ *Note* : This includes both the ``TTL`` set to 10 minutes and the
273
+ required matching ``Content-Type`` header for ``wns/toast``.
274
+
275
+ .. code:: bash
276
+
277
+ pywebpush --data stuff_to_send.xml \
278
+ --info edge_user_info.json \
279
+ --head windows_headers.json \
280
+ --claims vapid_claims.json
281
+
282
+ Google Cloud Messaging (GCM)
283
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
284
+
285
+ Please note that GCM has been sunset by Google. Providers should use
286
+ `Firebase Cloud
287
+ Messaging <https://firebase.google.com/support/troubleshooter/fcm/tokens/gcm>`__
288
+ instead.
289
+
290
+ Please also note that sending messages directly to FCM is not supported
291
+ by this library. In the past, you could use an insecure ``gcm_key`` as a
292
+ proxy for the authentication service. `This was disabled in June
293
+ 2024 <https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-legacy-protocol-send-requests>`__.
294
+
295
+ Sending WebPush messages to Google Chrome users should not be impacted
296
+ by this change.
@@ -9,7 +9,8 @@ build-backend = "setuptools.build_meta"
9
9
 
10
10
  [project]
11
11
  name = "pywebpush"
12
- version = "2.1.2"
12
+ version = "2.2.0"
13
+ requires-python = ">= 3.10"
13
14
  license = { text = "MPL-2.0" }
14
15
  authors = [{ name = "JR Conlin", email = "src+webpusher@jrconlin.com" }]
15
16
  description = "WebPush publication library"
@@ -19,7 +19,6 @@ except ImportError: # pragma nocover
19
19
  import aiohttp
20
20
  import http_ece
21
21
  import requests
22
- import six
23
22
  from cryptography.hazmat.backends import default_backend
24
23
  from cryptography.hazmat.primitives.asymmetric import ec
25
24
  from cryptography.hazmat.primitives import serialization
@@ -175,7 +174,7 @@ class WebPusher:
175
174
  for k in ["p256dh", "auth"]:
176
175
  if keys.get(k) is None:
177
176
  raise WebPushException("Missing keys value: {}".format(k))
178
- if isinstance(keys[k], six.text_type):
177
+ if isinstance(keys[k], str):
179
178
  keys[k] = bytes(cast(str, keys[k]).encode("utf8"))
180
179
  receiver_raw = base64.urlsafe_b64decode(
181
180
  self._repad(cast(bytes, keys["p256dh"]))
@@ -236,7 +235,7 @@ class WebPusher:
236
235
  format=serialization.PublicFormat.UncompressedPoint,
237
236
  )
238
237
 
239
- if isinstance(data, six.text_type):
238
+ if isinstance(data, str):
240
239
  data = bytes(data.encode("utf8"))
241
240
  if content_encoding == "aes128gcm":
242
241
  self.verb("Encrypting to aes128gcm...")
@@ -304,10 +303,7 @@ class WebPusher:
304
303
  data: Union[None, bytes] = None,
305
304
  headers: Union[None, Dict[str, str]] = None,
306
305
  ttl: int = 0,
307
- gcm_key: Union[None, str] = None,
308
- reg_id: Union[None, str] = None,
309
306
  content_encoding: str = "aes128gcm",
310
- curl: bool = False,
311
307
  ) -> dict:
312
308
  """Encode and send the data to the Push Service.
313
309
 
@@ -319,16 +315,8 @@ class WebPusher:
319
315
  recipient is not online. (Defaults to "0", which discards the
320
316
  message immediately if the recipient is unavailable.)
321
317
  :type ttl: int
322
- :param gcm_key: API key obtained from the Google Developer Console.
323
- Needed if endpoint is https://android.googleapis.com/gcm/send
324
- :type gcm_key: string
325
- :param reg_id: registration id of the recipient. If not provided,
326
- it will be extracted from the endpoint.
327
- :type reg_id: str
328
318
  :param content_encoding: ECE content encoding (defaults to "aes128gcm")
329
319
  :type content_encoding: str
330
- :param curl: Display output as `curl` command instead of sending
331
- :type curl: bool
332
320
  """
333
321
  # Encode the data.
334
322
  if headers is None:
@@ -355,40 +343,8 @@ class WebPusher:
355
343
  "content-encoding": content_encoding,
356
344
  }
357
345
  )
358
- if gcm_key:
359
- # guess if it is a legacy GCM project key or actual FCM key
360
- # gcm keys are all about 40 chars (use 100 for confidence),
361
- # fcm keys are 153-175 chars
362
- if len(gcm_key) < 100:
363
- self.verb("Guessing this is legacy GCM...")
364
- endpoint = "https://android.googleapis.com/gcm/send"
365
- else:
366
- self.verb("Guessing this is FCM...")
367
- endpoint = "https://fcm.googleapis.com/fcm/send"
368
- reg_ids = []
369
- if not reg_id:
370
- reg_id = cast(str, self.subscription_info["endpoint"]).rsplit("/", 1)[
371
- -1
372
- ]
373
- self.verb("Fetching out registration id: {}", reg_id)
374
- reg_ids.append(reg_id)
375
- gcm_data = dict()
376
- gcm_data["registration_ids"] = reg_ids
377
- if data:
378
- buffer = encoded.get("body")
379
- if buffer:
380
- gcm_data["raw_data"] = base64.b64encode(buffer).decode("utf8")
381
- gcm_data["time_to_live"] = int(headers["ttl"] if "ttl" in headers else ttl)
382
- encoded_data = json.dumps(gcm_data)
383
- headers.update(
384
- {
385
- "Authorization": "key=" + gcm_key,
386
- "Content-Type": "application/json",
387
- }
388
- )
389
- else:
390
- encoded_data = encoded.get("body")
391
- endpoint = self.subscription_info["endpoint"]
346
+ encoded_data = encoded.get("body")
347
+ endpoint = self.subscription_info["endpoint"]
392
348
 
393
349
  if "ttl" not in headers or ttl:
394
350
  self.verb("Generating TTL of 0...")
@@ -424,9 +380,10 @@ class WebPusher:
424
380
  **params,
425
381
  )
426
382
  self.verb(
427
- "\nResponse:\n\tcode: {}\n\tbody: {}\n",
383
+ "\nResponse:\n\tcode: {}\n\tbody: {}\n\theaders: {}",
428
384
  resp.status_code,
429
385
  resp.text or "Empty",
386
+ resp.headers or "None"
430
387
  )
431
388
  return resp
432
389
 
@@ -2,6 +2,7 @@ import argparse
2
2
  import os
3
3
  import json
4
4
  import logging
5
+ import math
5
6
 
6
7
  from requests import JSONDecodeError
7
8
 
@@ -15,6 +16,11 @@ def get_config():
15
16
  parser.add_argument("--head", help="Header Info JSON file")
16
17
  parser.add_argument("--claims", help="Vapid claim file")
17
18
  parser.add_argument("--key", help="Vapid private key file path")
19
+ parser.add_argument(
20
+ "--wns",
21
+ help="Include WNS cache header based on TTL",
22
+ default=False,
23
+ action="store_true")
18
24
  parser.add_argument(
19
25
  "--curl",
20
26
  help="Don't send, display as curl command",
@@ -53,6 +59,15 @@ def get_config():
53
59
  args.head = json.loads(r.read())
54
60
  except JSONDecodeError as e:
55
61
  raise WebPushException("Could not read the header arguments: {}", e)
62
+ # Set the default "TTL"
63
+ args.head["ttl"] = args.head.get("ttl", "0")
64
+ if args.wns:
65
+ # NOTE: Microsoft also requires `X-WNS-Type` as
66
+ # `tile`, `toast`, `badge` or `raw`. This is not provided by this code.
67
+ if int(args.head.get("ttl", "0")) > 0:
68
+ args.head["x-wns-cache-policy"] = "cache"
69
+ else:
70
+ args.head["x-wns-cache-policy"] = "no-cache"
56
71
  if args.claims:
57
72
  if not args.key:
58
73
  raise WebPushException("No private --key specified for claims")
@@ -329,21 +329,6 @@ class WebpushTestUtils(unittest.TestCase):
329
329
  del ci["FOO"]
330
330
  assert ci.get("Foo") is None
331
331
 
332
- @patch("requests.post")
333
- def test_gcm(self, mock_post):
334
- subscription_info = self._gen_subscription_info(
335
- None, endpoint="https://android.googleapis.com/gcm/send/regid123"
336
- )
337
- headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"}
338
- data = "Mary had a little lamb"
339
- wp = WebPusher(subscription_info)
340
- wp.send(data, headers, gcm_key="gcm_key_value")
341
- pdata = json.loads(mock_post.call_args[1].get("data"))
342
- pheaders = mock_post.call_args[1].get("headers")
343
- assert pdata["registration_ids"][0] == "regid123"
344
- assert pheaders.get("authorization") == "key=gcm_key_value"
345
- assert pheaders.get("content-type") == "application/json"
346
-
347
332
  @patch("requests.post")
348
333
  def test_timeout(self, mock_post):
349
334
  mock_post.return_value.status_code = 200
@@ -406,21 +391,6 @@ class WebPusherAsyncTestCase(WebpushTestUtils, unittest.IsolatedAsyncioTestCase)
406
391
  assert pheaders.get("ttl") == "0"
407
392
  assert pheaders.get("content-encoding") == "aes128gcm"
408
393
 
409
- @patch("aiohttp.ClientSession.post", new_callable=AsyncMock)
410
- async def test_fcm(self, mock_post):
411
- subscription_info = self._gen_subscription_info(
412
- None, endpoint="https://android.googleapis.com/fcm/send/regid123"
413
- )
414
- headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"}
415
- data = "Mary had a little lamb"
416
- wp = WebPusher(subscription_info)
417
- await wp.send_async(data, headers, gcm_key="gcm_key_value")
418
- pdata = json.loads(mock_post.call_args[1].get("data"))
419
- pheaders = mock_post.call_args[1].get("headers")
420
- assert pdata["registration_ids"][0] == "regid123"
421
- assert pheaders.get("authorization") == "key=gcm_key_value"
422
- assert pheaders.get("content-type") == "application/json"
423
-
424
394
  @patch("aiohttp.ClientSession.post", new_callable=AsyncMock)
425
395
  async def test_timeout(self, mock_post):
426
396
  mock_post.return_value.status_code = 200
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebpush
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: WebPush publication library
5
5
  Author-email: JR Conlin <src+webpusher@jrconlin.com>
6
6
  License: MPL-2.0
@@ -10,6 +10,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
10
10
  Classifier: Programming Language :: Python :: Implementation :: PyPy
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
13
14
  Description-Content-Type: text/markdown
14
15
  License-File: LICENSE
15
16
  Requires-Dist: aiohttp
@@ -26,9 +27,6 @@ Dynamic: license-file
26
27
 
27
28
  # Webpush Data encryption library for Python
28
29
 
29
- [![Build Status](https://travis-ci.org/web-push-libs/pywebpush.svg?branch=main)](https://travis-ci.org/web-push-libs/pywebpush)
30
- [![Requirements Status](https://requires.io/github/web-push-libs/pywebpush/requirements.svg?branch=main)](https://requires.io/github/web-push-libs/pywebpush/requirements/?branch=main)
31
-
32
30
  This library is available on [pypi as pywebpush](https://pypi.python.org/pypi/pywebpush).
33
31
  Source is available on [github](https://github.com/mozilla-services/pywebpush).
34
32
  Please note: This library was designated as a `Critical Project` by PyPi, it is currently
@@ -64,6 +62,11 @@ referenced to the user who requested it, and recall it when there's
64
62
  a new push subscription update is left as an exercise for the
65
63
  reader.
66
64
 
65
+ _*Note:*_ Some platforms (like Microsoft Windows) require additional
66
+ headers specified for the Push call. These additional headers are not
67
+ standard and may be rejected by other Push services. Please see
68
+ [Special Instructions](#special-instructions) below.
69
+
67
70
  ### Sending Data using `webpush()` One Call
68
71
 
69
72
  In many cases, your code will be sending a single message to many
@@ -82,7 +85,7 @@ webpush(subscription_info,
82
85
  This will encode `data`, add the appropriate VAPID auth headers if required and send it to the push server identified
83
86
  in the `subscription_info` block.
84
87
 
85
- ##### Parameters
88
+ #### Parameters
86
89
 
87
90
  _subscription_info_ - The `dict` of the subscription info (described above).
88
91
 
@@ -107,7 +110,7 @@ e.g. the output of:
107
110
  openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
108
111
  ```
109
112
 
110
- ##### Example
113
+ #### Example
111
114
 
112
115
  ```python
113
116
  from pywebpush import webpush, WebPushException
@@ -145,7 +148,7 @@ can pass just `wp = WebPusher(subscription_info)`. This will return a `WebPusher
145
148
 
146
149
  The following methods are available:
147
150
 
148
- #### `.send(data, headers={}, ttl=0, gcm_key="", reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
151
+ #### `.send(data, headers={}, ttl=0, reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)`
149
152
 
150
153
  Send the data using additional parameters. On error, returns a `WebPushException`
151
154
 
@@ -157,9 +160,6 @@ _headers_ A `dict` containing any additional headers to send
157
160
 
158
161
  _ttl_ Message Time To Live on Push Server waiting for the client to reconnect (in seconds)
159
162
 
160
- _gcm_key_ Google Cloud Messaging key (if using the older GCM push system) This is the API key obtained from the Google
161
- Developer Console.
162
-
163
163
  _reg_id_ Google Cloud Messaging registration ID (will be extracted from endpoint if not specified)
164
164
 
165
165
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
@@ -172,10 +172,10 @@ See [requests documentation](http://docs.python-requests.org/en/master/user/quic
172
172
 
173
173
  ##### Example
174
174
 
175
- to send from Chrome using the old GCM mode:
175
+ to send to a user on Chrome:
176
176
 
177
177
  ```python
178
- WebPusher(subscription_info).send(data, headers, ttl, gcm_key)
178
+ WebPusher(subscription_info).send(data, headers, ttl)
179
179
  ```
180
180
 
181
181
  #### `.encode(data, content_encoding="aes128gcm")`
@@ -188,7 +188,7 @@ _data_ Binary string of data to send
188
188
 
189
189
  _content_encoding_ ECE content encoding type (defaults to "aes128gcm")
190
190
 
191
- *Note* This will return a `NoData` exception if the data is not present or empty. It is completely
191
+ _*Note*_ This will return a `NoData` exception if the data is not present or empty. It is completely
192
192
  valid to send a WebPush notification with no data, but encoding is a no-op in that case. Best not
193
193
  to call it if you don't have data.
194
194
 
@@ -200,8 +200,7 @@ encoded_data = WebPush(subscription_info).encode(data)
200
200
 
201
201
  ## Stand Alone Webpush
202
202
 
203
- If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the
204
- ./bin directory.
203
+ If you're not really into coding your own solution, there's also a "stand-alone" `pywebpush` command in the `./bin` directory.
205
204
 
206
205
  This uses two files:
207
206
 
@@ -227,3 +226,40 @@ If you're interested in just testing your applications WebPush interface, you co
227
226
  which will encrypt and send the contents of `stuff_to_send.data`.
228
227
 
229
228
  See `./bin/pywebpush --help` for available commands and options.
229
+
230
+ ## Special Instructions
231
+
232
+ ### Windows
233
+
234
+ [Microsoft requires](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/push-request-response-headers#request-parameters)
235
+ one extra header and suggests several additional headers. Users have reported that not including
236
+ these headers can cause notifications to fail to appear. These additional headers are non-standard
237
+ and may be rejected by other platforms. You should be cautious including them with calls to
238
+ non-Microsoft platforms, or to non-Microsoft destinations.
239
+
240
+ As of 2024-Apr-19, Microsoft requires an `X-WNS-Type` header. As an example, you can include this
241
+ header within the command line call:
242
+
243
+ Content of the `windows_headers.json` file:
244
+
245
+ ```json
246
+ {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}
247
+ ```
248
+
249
+ _*Note*_ : This includes both the `TTL` set to 10 minutes and the required matching `Content-Type` header for `wns/toast`.
250
+
251
+ ```bash
252
+ pywebpush --data stuff_to_send.xml \
253
+ --info edge_user_info.json \
254
+ --head windows_headers.json \
255
+ --claims vapid_claims.json
256
+ ```
257
+
258
+ ### Google Cloud Messaging (GCM)
259
+
260
+ Please note that GCM has been sunset by Google. Providers should
261
+ use [Firebase Cloud Messaging](https://firebase.google.com/support/troubleshooter/fcm/tokens/gcm) instead.
262
+
263
+ Please also note that sending messages directly to FCM is not supported by this library. In the past, you could use an insecure `gcm_key` as a proxy for the authentication service. [This was disabled in June 2024](https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-legacy-protocol-send-requests).
264
+
265
+ Sending WebPush messages to Google Chrome users should not be impacted by this change.
@@ -2,7 +2,6 @@ CHANGELOG.md
2
2
  CODE_OF_CONDUCT.md
3
3
  LICENSE
4
4
  MANIFEST.in
5
- PULL_REQUEST_TEMPLATE.md
6
5
  README.md
7
6
  README.rst
8
7
  entry_points.txt
@@ -1,13 +0,0 @@
1
- ## Description
2
-
3
- *_NOTE_*: All commits MUST be signed! See https://docs.github.com/en/github/authenticating-to-github/signing-commits
4
-
5
- _Describe these changes._
6
-
7
- ## Testing
8
-
9
- _How should reviewers test?_
10
-
11
- ## Issue(s)
12
-
13
- Closes _#IssueNumber_
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes