@@ -116,8 +116,10 @@ class WebPusher:
116116 "aesgcm" , # draft-httpbis-encryption-encoding-01
117117 "aes128gcm" # RFC8188 Standard encoding
118118 ]
119+ verbose = False
119120
120- def __init__ (self , subscription_info , requests_session = None ):
121+ def __init__ (self , subscription_info , requests_session = None ,
122+ verbose = False ):
121123 """Initialize using the info provided by the client PushSubscription
122124 object (See
123125 https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe)
@@ -130,7 +132,12 @@ def __init__(self, subscription_info, requests_session=None):
130132 to the same client.
131133 :type requests_session: requests.Session
132134
135+ :param verbose: provide verbose feedback
136+ :type verbose: bool
137+
133138 """
139+
140+ self .verbose = verbose
134141 if requests_session is None :
135142 self .requests_method = requests
136143 else :
@@ -155,6 +162,10 @@ def __init__(self, subscription_info, requests_session=None):
155162 self .auth_key = base64 .urlsafe_b64decode (
156163 self ._repad (keys ['auth' ]))
157164
165+ def verb (self , msg , * args , ** kwargs ):
166+ if self .verbose :
167+ print (msg .format (* args , ** kwargs ))
168+
158169 def _repad (self , data ):
159170 """Add base64 padding to the end of a string, if required"""
160171 return data + b"====" [:len (data ) % 4 ]
@@ -174,15 +185,18 @@ def encode(self, data, content_encoding="aes128gcm"):
174185 """
175186 # Salt is a random 16 byte array.
176187 if not data :
188+ self .verb ("No data found..." )
177189 return
178190 if not self .auth_key or not self .receiver_key :
179191 raise WebPushException ("No keys specified in subscription info" )
192+ self .verb ("Encoding data..." )
180193 salt = None
181194 if content_encoding not in self .valid_encodings :
182195 raise WebPushException ("Invalid content encoding specified. "
183196 "Select from " +
184197 json .dumps (self .valid_encodings ))
185198 if content_encoding == "aesgcm" :
199+ self .verb ("Generating salt for aesgcm..." )
186200 salt = os .urandom (16 )
187201 # The server key is an ephemeral ECDH key used only for this
188202 # transaction
@@ -195,6 +209,7 @@ def encode(self, data, content_encoding="aes128gcm"):
195209 if isinstance (data , six .string_types ):
196210 data = bytes (data .encode ('utf8' ))
197211 if content_encoding == "aes128gcm" :
212+ self .verb ("Encrypting to aes128gcm..." )
198213 encrypted = http_ece .encrypt (
199214 data ,
200215 salt = salt ,
@@ -206,6 +221,7 @@ def encode(self, data, content_encoding="aes128gcm"):
206221 'body' : encrypted
207222 })
208223 else :
224+ self .verb ("Encrypting to aesgcm..." )
209225 crypto_key = base64 .urlsafe_b64encode (crypto_key ).strip (b'=' )
210226 encrypted = http_ece .encrypt (
211227 data ,
@@ -248,6 +264,7 @@ def as_curl(self, endpoint, encoded_data, headers):
248264 f .write (encoded_data )
249265 data = "--data-binary @encrypted.data"
250266 if 'content-length' not in headers :
267+ self .verb ("Generating content-length header..." )
251268 header_list .append (
252269 '-H "content-length: {}" \\ \n ' .format (len (encoded_data )))
253270 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,
312329 # gcm keys are all about 40 chars (use 100 for confidence),
313330 # fcm keys are 153-175 chars
314331 if len (gcm_key ) < 100 :
332+ self .verb ("Guessing this is legacy GCM..." )
315333 endpoint = 'https://android.googleapis.com/gcm/send'
316334 else :
335+ self .verb ("Guessing this is FCM..." )
317336 endpoint = 'https://fcm.googleapis.com/fcm/send'
318337 reg_ids = []
319338 if not reg_id :
320339 reg_id = self .subscription_info ['endpoint' ].rsplit ('/' , 1 )[- 1 ]
340+ self .verb ("Fetching out registration id: {}" , reg_id )
321341 reg_ids .append (reg_id )
322342 gcm_data = dict ()
323343 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,
336356 endpoint = self .subscription_info ['endpoint' ]
337357
338358 if 'ttl' not in headers or ttl :
359+ self .verb ("Generating TTL of 0..." )
339360 headers ['ttl' ] = str (ttl or 0 )
340361 # Additionally useful headers:
341362 # Authorization / Crypto-Key (VAPID headers)
342363 if curl :
343364 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 ,
345369 data = encoded_data ,
346370 headers = headers ,
347371 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
348375
349376
350377def webpush (subscription_info ,
@@ -354,7 +381,8 @@ def webpush(subscription_info,
354381 content_encoding = "aes128gcm" ,
355382 curl = False ,
356383 timeout = None ,
357- ttl = 0 ):
384+ ttl = 0 ,
385+ verbose = False ):
358386 """
359387 One call solution to endcode and send `data` to the endpoint
360388 contained in `subscription_info` using optional VAPID auth headers.
@@ -396,11 +424,15 @@ def webpush(subscription_info,
396424 :type timeout: float or tuple
397425 :param ttl: Time To Live
398426 :type ttl: int
427+ :param verbose: Provide verbose feedback
428+ :type verbose: bool
399429 :return requests.Response or string
400430
401431 """
402432 vapid_headers = None
403433 if vapid_claims :
434+ if verbose :
435+ print ("Generating VAPID headers..." )
404436 if not vapid_claims .get ('aud' ):
405437 url = urlparse (subscription_info .get ('endpoint' ))
406438 aud = "{}://{}" .format (url .scheme , url .netloc )
@@ -411,6 +443,9 @@ def webpush(subscription_info,
411443 or vapid_claims .get ('exp' ) < int (time .time ())):
412444 # encryption lives for 12 hours
413445 vapid_claims ['exp' ] = int (time .time ()) + (12 * 60 * 60 )
446+ if verbose :
447+ print ("Setting VAPID expry to {}..." .format (
448+ vapid_claims ['exp' ]))
414449 if not vapid_private_key :
415450 raise WebPushException ("VAPID dict missing 'private_key'" )
416451 if isinstance (vapid_private_key , Vapid ):
@@ -422,8 +457,13 @@ def webpush(subscription_info,
422457 private_key_file = vapid_private_key ) # pragma no cover
423458 else :
424459 vv = Vapid .from_string (private_key = vapid_private_key )
460+ if verbose :
461+ print ("\t claims: {}" .format (vapid_claims ))
425462 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 (
427467 data ,
428468 vapid_headers ,
429469 ttl = ttl ,
@@ -432,7 +472,7 @@ def webpush(subscription_info,
432472 timeout = timeout ,
433473 )
434474 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 ),
437477 response = response )
438478 return response
0 commit comments