@@ -116,8 +116,10 @@ class WebPusher:
116
116
"aesgcm" , # draft-httpbis-encryption-encoding-01
117
117
"aes128gcm" # RFC8188 Standard encoding
118
118
]
119
+ verbose = False
119
120
120
- def __init__ (self , subscription_info , requests_session = None ):
121
+ def __init__ (self , subscription_info , requests_session = None ,
122
+ verbose = False ):
121
123
"""Initialize using the info provided by the client PushSubscription
122
124
object (See
123
125
https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe)
@@ -130,7 +132,12 @@ def __init__(self, subscription_info, requests_session=None):
130
132
to the same client.
131
133
:type requests_session: requests.Session
132
134
135
+ :param verbose: provide verbose feedback
136
+ :type verbose: bool
137
+
133
138
"""
139
+
140
+ self .verbose = verbose
134
141
if requests_session is None :
135
142
self .requests_method = requests
136
143
else :
@@ -155,6 +162,10 @@ def __init__(self, subscription_info, requests_session=None):
155
162
self .auth_key = base64 .urlsafe_b64decode (
156
163
self ._repad (keys ['auth' ]))
157
164
165
+ def verb (self , msg , * args , ** kwargs ):
166
+ if self .verbose :
167
+ print (msg .format (* args , ** kwargs ))
168
+
158
169
def _repad (self , data ):
159
170
"""Add base64 padding to the end of a string, if required"""
160
171
return data + b"====" [:len (data ) % 4 ]
@@ -174,15 +185,18 @@ def encode(self, data, content_encoding="aes128gcm"):
174
185
"""
175
186
# Salt is a random 16 byte array.
176
187
if not data :
188
+ self .verb ("No data found..." )
177
189
return
178
190
if not self .auth_key or not self .receiver_key :
179
191
raise WebPushException ("No keys specified in subscription info" )
192
+ self .verb ("Encoding data..." )
180
193
salt = None
181
194
if content_encoding not in self .valid_encodings :
182
195
raise WebPushException ("Invalid content encoding specified. "
183
196
"Select from " +
184
197
json .dumps (self .valid_encodings ))
185
198
if content_encoding == "aesgcm" :
199
+ self .verb ("Generating salt for aesgcm..." )
186
200
salt = os .urandom (16 )
187
201
# The server key is an ephemeral ECDH key used only for this
188
202
# transaction
@@ -195,6 +209,7 @@ def encode(self, data, content_encoding="aes128gcm"):
195
209
if isinstance (data , six .string_types ):
196
210
data = bytes (data .encode ('utf8' ))
197
211
if content_encoding == "aes128gcm" :
212
+ self .verb ("Encrypting to aes128gcm..." )
198
213
encrypted = http_ece .encrypt (
199
214
data ,
200
215
salt = salt ,
@@ -206,6 +221,7 @@ def encode(self, data, content_encoding="aes128gcm"):
206
221
'body' : encrypted
207
222
})
208
223
else :
224
+ self .verb ("Encrypting to aesgcm..." )
209
225
crypto_key = base64 .urlsafe_b64encode (crypto_key ).strip (b'=' )
210
226
encrypted = http_ece .encrypt (
211
227
data ,
@@ -248,6 +264,7 @@ def as_curl(self, endpoint, encoded_data, headers):
248
264
f .write (encoded_data )
249
265
data = "--data-binary @encrypted.data"
250
266
if 'content-length' not in headers :
267
+ self .verb ("Generating content-length header..." )
251
268
header_list .append (
252
269
'-H "content-length: {}" \\ \n ' .format (len (encoded_data )))
253
270
return ("""curl -vX POST {url} \\ \n {headers}{data}""" .format (
@@ -312,12 +329,15 @@ def send(self, data=None, headers=None, ttl=0, gcm_key=None, reg_id=None,
312
329
# gcm keys are all about 40 chars (use 100 for confidence),
313
330
# fcm keys are 153-175 chars
314
331
if len (gcm_key ) < 100 :
332
+ self .verb ("Guessing this is legacy GCM..." )
315
333
endpoint = 'https://android.googleapis.com/gcm/send'
316
334
else :
335
+ self .verb ("Guessing this is FCM..." )
317
336
endpoint = 'https://fcm.googleapis.com/fcm/send'
318
337
reg_ids = []
319
338
if not reg_id :
320
339
reg_id = self .subscription_info ['endpoint' ].rsplit ('/' , 1 )[- 1 ]
340
+ self .verb ("Fetching out registration id: {}" , reg_id )
321
341
reg_ids .append (reg_id )
322
342
gcm_data = dict ()
323
343
gcm_data ['registration_ids' ] = reg_ids
@@ -336,15 +356,22 @@ def send(self, data=None, headers=None, ttl=0, gcm_key=None, reg_id=None,
336
356
endpoint = self .subscription_info ['endpoint' ]
337
357
338
358
if 'ttl' not in headers or ttl :
359
+ self .verb ("Generating TTL of 0..." )
339
360
headers ['ttl' ] = str (ttl or 0 )
340
361
# Additionally useful headers:
341
362
# Authorization / Crypto-Key (VAPID headers)
342
363
if curl :
343
364
return self .as_curl (endpoint , encoded_data , headers )
344
- return self .requests_method .post (endpoint ,
365
+ self .verb ("\n Sending request to"
366
+ "\n \t host: {}\n \t headers: {}\n \t data: {}" ,
367
+ endpoint , headers , encoded_data )
368
+ resp = self .requests_method .post (endpoint ,
345
369
data = encoded_data ,
346
370
headers = headers ,
347
371
timeout = timeout )
372
+ self .verb ("\n Response:\n \t code: {}\n \t body: {}\n " ,
373
+ resp .status_code , resp .text or "Empty" )
374
+ return resp
348
375
349
376
350
377
def webpush (subscription_info ,
@@ -354,7 +381,8 @@ def webpush(subscription_info,
354
381
content_encoding = "aes128gcm" ,
355
382
curl = False ,
356
383
timeout = None ,
357
- ttl = 0 ):
384
+ ttl = 0 ,
385
+ verbose = False ):
358
386
"""
359
387
One call solution to endcode and send `data` to the endpoint
360
388
contained in `subscription_info` using optional VAPID auth headers.
@@ -396,11 +424,15 @@ def webpush(subscription_info,
396
424
:type timeout: float or tuple
397
425
:param ttl: Time To Live
398
426
:type ttl: int
427
+ :param verbose: Provide verbose feedback
428
+ :type verbose: bool
399
429
:return requests.Response or string
400
430
401
431
"""
402
432
vapid_headers = None
403
433
if vapid_claims :
434
+ if verbose :
435
+ print ("Generating VAPID headers..." )
404
436
if not vapid_claims .get ('aud' ):
405
437
url = urlparse (subscription_info .get ('endpoint' ))
406
438
aud = "{}://{}" .format (url .scheme , url .netloc )
@@ -411,6 +443,9 @@ def webpush(subscription_info,
411
443
or vapid_claims .get ('exp' ) < int (time .time ())):
412
444
# encryption lives for 12 hours
413
445
vapid_claims ['exp' ] = int (time .time ()) + (12 * 60 * 60 )
446
+ if verbose :
447
+ print ("Setting VAPID expry to {}..." .format (
448
+ vapid_claims ['exp' ]))
414
449
if not vapid_private_key :
415
450
raise WebPushException ("VAPID dict missing 'private_key'" )
416
451
if isinstance (vapid_private_key , Vapid ):
@@ -422,8 +457,13 @@ def webpush(subscription_info,
422
457
private_key_file = vapid_private_key ) # pragma no cover
423
458
else :
424
459
vv = Vapid .from_string (private_key = vapid_private_key )
460
+ if verbose :
461
+ print ("\t claims: {}" .format (vapid_claims ))
425
462
vapid_headers = vv .sign (vapid_claims )
426
- response = WebPusher (subscription_info ).send (
463
+ if verbose :
464
+ print ("\t headers: {}" .format (vapid_headers ))
465
+
466
+ response = WebPusher (subscription_info , verbose = verbose ).send (
427
467
data ,
428
468
vapid_headers ,
429
469
ttl = ttl ,
@@ -432,7 +472,7 @@ def webpush(subscription_info,
432
472
timeout = timeout ,
433
473
)
434
474
if not curl and response .status_code > 202 :
435
- raise WebPushException ("Push failed: {} {}" .format (
436
- response .status_code , response .reason ),
475
+ raise WebPushException ("Push failed: {} {}\n Response body:{} " .format (
476
+ response .status_code , response .reason , response . text ),
437
477
response = response )
438
478
return response
0 commit comments