def send_notification(self, title, options, ttl=86400): subscription = { "endpoint": self.endpoint, "keys": { "auth": self.auth, "p256dh": self.p256dh } } payload = {"title": title, "options": options or {}} WebPusher(subscription).\ send(json.dumps(payload), {}, ttl, GCM_KEY)
def test_encode_no_crypto(self): subscription_info = self._gen_subscription_info() del(subscription_info['keys']) headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"} data = 'Something' pusher = WebPusher(subscription_info) assert_raises(WebPushException, pusher.encode, data, headers)
def test_send_empty(self, mock_post): subscription_info = self._gen_subscription_info() headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"} WebPusher(subscription_info).send('', headers) eq_(subscription_info.get('endpoint'), mock_post.call_args[0][0]) pheaders = mock_post.call_args[1].get('headers') eq_(pheaders.get('ttl'), '0') ok_('encryption' not in pheaders) eq_(pheaders.get('AUTHENTICATION'), headers.get('Authentication')) ckey = pheaders.get('crypto-key') ok_('pre-existing' in ckey)
async def process_user(user, args, headers): """Send a message to a user or drop the user if no longer valid. :param user: UserID of customer to attempt to send Subscription Update :type user: string :param args: settings :type args: object :param headers: Additional headers to send :type headers: dict :returns: UserID of failed sends """ sub_info = json.loads(user["subinfo"]) try: result = WebPusher(sub_info).send( args.msg, headers=headers, ttl=args.ttl) print("Result: {}".format(result.status_code)) if result.status_code > requests.codes.ok: # Remove any users that no longer want updates. if result.status_code in [404, 410]: try: reply = json.loads(result.text) reason = reply.get( "message", "Unknown reason") if 'more_info' in reply: reason += ( "\nFor more info, see: " + reply['more_info'] ) except: reason = "and couldn't understand {}".format( result.text ) log.error( "Failed to send to {}: {}".format( user["id"], reason, ) ) await drop_user(args, user["id"]) args.db.commit() return None result.raise_for_status() log.info("Sent message to {}".format(user["id"])) return None except Exception as x: log.error("Could not process user {}".format(repr(x))) return user["id"]
def simple_push_notification(sender, receiver, notiftype, teaser=''): """ Send a Push Notification to all registered endpoints of user 'receiver'. """ data = {} ttl = 120 gcm_url = 'https://android.googleapis.com/gcm/send' endpoints = receiver.endpoints.all() if settings.DEBUG: print('- ' * 30) print('# simple_push_notification() with:') print('# sender: {}'.format(sender)) print('# receiver: {}'.format(receiver)) print('# notiftype: {}'.format(notiftype)) print('# teaser: {}'.format(teaser)) print('# endpoints: {}'.format(endpoints.count())) for obj in endpoints: # Special case Google: needs gcm_key as API key. gcm_key = settings.GCM_AUTHKEY if gcm_url in obj.sub else None # Make sure we only send one per minute at most. Further limit it on # the client device. if (obj.latest + timedelta(minutes=1)) > now(): return False obj.latest = now() obj.save() if settings.DEBUG: print('- ' * 30) print('# obj.sub: {}'.format(obj.sub)) subscription_info = json.loads(obj.sub) if settings.DEBUG: print('# sub...info: {}'.format(subscription_info)) headers = {'Content-Type': 'application/json'} data['notiftype'] = notiftype # 'message', 'upvote', etc. data['username'] = sender.username # Sender's username. data['teaser'] = teaser # A few words of a message received, if any. data_str = json.dumps(data, ensure_ascii=True) if settings.DEBUG: print('# data_str: {}'.format(data_str)) try: WebPusher(subscription_info).send(data_str, headers, ttl, gcm_key) except WebPushException as e: # "subscription_info missing keys dictionary" if settings.DEBUG: print('WebPushException: {}'.format(e))
def _send_notification(push_info, payload, ttl): subscription = push_info.subscription subscription_data = _process_subscription_info(subscription) # Check if GCM info is provided in the settings if hasattr(settings, 'WEBPUSH_SETTINGS'): gcm_key = settings.WEBPUSH_SETTINGS.get('GCM_KEY') else: gcm_key = None req = WebPusher(subscription_data).send(data=payload, ttl=ttl, gcm_key=gcm_key) return req
def _push_message(self, payload, **kwargs): """Send the message.""" import jwt from pywebpush import WebPusher timestamp = int(time.time()) payload['timestamp'] = (timestamp * 1000) # Javascript ms since epoch targets = kwargs.get(ATTR_TARGET) if not targets: targets = self.registrations.keys() for target in list(targets): info = self.registrations.get(target) if info is None: _LOGGER.error( "%s is not a valid HTML5 push notification" " target", target) continue jwt_exp = (datetime.datetime.fromtimestamp(timestamp) + datetime.timedelta(days=JWT_VALID_DAYS)) jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] jwt_claims = { 'exp': jwt_exp, 'nbf': timestamp, 'iat': timestamp, ATTR_TARGET: target, ATTR_TAG: payload[ATTR_TAG] } jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8') payload[ATTR_DATA][ATTR_JWT] = jwt_token # Only pass the gcm key if we're actually using GCM # If we don't, notifications break on FireFox gcm_key = self._gcm_key \ if 'googleapis.com' in info[ATTR_SUBSCRIPTION][ATTR_ENDPOINT] \ else None response = WebPusher(info[ATTR_SUBSCRIPTION]).send( json.dumps(payload), gcm_key=gcm_key, ttl='86400') if response.status_code == 410: _LOGGER.info("Notification channel has expired") reg = self.registrations.pop(target) if not save_json(self.registrations_json_path, self.registrations): self.registrations[target] = reg _LOGGER.error("Error saving registration") else: _LOGGER.info("Configuration saved")
def test_gcm(self, mock_post): subscription_info = self._gen_subscription_info( None, endpoint="https://android.googleapis.com/gcm/send/regid123") headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"} data = "Mary had a little lamb" wp = WebPusher(subscription_info) wp.send(data, headers, gcm_key="gcm_key_value") pdata = json.loads(mock_post.call_args[1].get('data')) pheaders = mock_post.call_args[1].get('headers') eq_(pdata["registration_ids"][0], "regid123") eq_(pheaders.get("authorization"), "key=gcm_key_value") eq_(pheaders.get("content-type"), "application/json")
def test_init(self): # use static values so we know what to look for in the reply subscription_info = { u"endpoint": u"https://example.com/", u"keys": { u"p256dh": (u"BOrnIslXrUow2VAzKCUAE4sIbK00daEZCswOcf8m3T" "F8V82B-OpOg5JbmYLg44kRcvQC1E2gMJshsUYA-_zMPR8"), u"auth": u"k8JV6sjdbhAi1n3_LDBLvA" } } self.assertRaises(WebPushException, WebPusher, {"keys": { 'p256dh': 'AAA=', 'auth': 'AAA=' }}) self.assertRaises(WebPushException, WebPusher, {"endpoint": "https://example.com"}) self.assertRaises(WebPushException, WebPusher, { "endpoint": "https://example.com", "keys": { 'p256dh': 'AAA=' } }) self.assertRaises(WebPushException, WebPusher, { "endpoint": "https://example.com", "keys": { 'auth': 'AAA=' } }) self.assertRaises( WebPushException, WebPusher, { "endpoint": "https://example.com", "keys": { 'p256dh': 'AAA=', 'auth': 'AAA=' } }) push = WebPusher(subscription_info) eq_(push.subscription_info, subscription_info) eq_( push.receiver_key, bytes_compat(b'\x04\xea\xe7"\xc9W\xadJ0\xd9P3(%\x00\x13\x8b' b'\x08l\xad4u\xa1\x19\n\xcc\x0eq\xff&\xdd1' b'|W\xcd\x81\xf8\xeaN\x83\x92[\x99\x82\xe0\xe3' b'\x89\x11r\xf4\x02\xd4M\xa00\x9b!\xb1F\x00' b'\xfb\xfc\xcc=\x1f')) eq_(push.auth_key, bytes_compat(b'\x93\xc2U\xea\xc8\xddn\x10"\xd6}\xff,0K\xbc'))
def test_send(self, mock_post): subscription_info = self._gen_subscription_info() headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"} data = "Mary had a little lamb" WebPusher(subscription_info).send(data, headers) eq_(subscription_info.get('endpoint'), mock_post.call_args[0][0]) pheaders = mock_post.call_args[1].get('headers') eq_(pheaders.get('ttl'), '0') ok_('encryption' in pheaders) eq_(pheaders.get('AUTHENTICATION'), headers.get('Authentication')) ckey = pheaders.get('crypto-key') ok_('pre-existing' in ckey) eq_(pheaders.get('content-encoding'), 'aesgcm')
def send_push_worker(data): ( subscr_list, payload, ttl, ) = data responses = [] exceptions = [] # can't use logger in a worker for subscr in subscr_list: try: if subscr.is_gcm(): response = WebPusher(subscr.endpoint_and_keys()).send( data=payload if subscr.supports_payload() else None, ttl=ttl, timeout=2, gcm_key=FCM_SERVER_KEY, # seems to be the only encoding legacy chrome understands # for payload encryption content_encoding="aesgcm") responses.append((subscr, response)) else: try: response = webpush( subscription_info=subscr.endpoint_and_keys(), data=payload if subscr.supports_payload() else None, ttl=ttl, timeout=2, vapid_private_key=VAPID_PRIVATE_KEY, vapid_claims={ "sub": "mailto:" + VAPID_ADMIN_EMAIL, # minus 15 minutes for clock differences between our server and # push service server. This is still better than default # minus 12 hours in pywebpush lib "exp": int(time.time()) + ttl - 15 * 60, }) responses.append((subscr, response)) except WebPushException as e: # WebPushException got response object but we need to count # error points on subscriptions responses.append((subscr, e.response)) except Exception as e: exceptions.append(( subscr, e, time.time(), )) return (responses, exceptions)
def test_send_using_requests_session(self, mock_session): subscription_info = self._gen_subscription_info() headers = { "Crypto-Key": "pre-existing", "Authentication": "bearer vapid" } data = "Mary had a little lamb" WebPusher(subscription_info, requests_session=mock_session).send(data, headers) assert subscription_info.get( 'endpoint') == mock_session.post.call_args[0][0] pheaders = mock_session.post.call_args[1].get('headers') assert pheaders.get('ttl') == '0' assert pheaders.get('AUTHENTICATION') == headers.get('Authentication') ckey = pheaders.get('crypto-key') assert 'pre-existing' in ckey assert pheaders.get('content-encoding') == 'aes128gcm'
def send_push_worker(data): ( subscr_list, payload, ttl, gcm_key, vapid_key, vapid_email, ) = data responses = [] exceptions = [] # can't use logger in a worker for subscr in subscr_list: try: if subscr.is_gcm(): response = WebPusher( subscr.endpoint_and_keys() ).send( data=payload if subscr.supports_payload() else None, ttl=ttl, timeout=3.0, gcm_key=gcm_key, # seems to be the only encoding legacy chrome understands # for payload encryption content_encoding="aesgcm" ) responses.append( (subscr, response) ) else: try: response = webpush( subscription_info=subscr.endpoint_and_keys(), data=payload if subscr.supports_payload() else None, ttl=ttl, timeout=3.0, vapid_private_key=vapid_key, vapid_claims={"sub": "mailto:"+vapid_email,} ) responses.append( (subscr, response) ) except WebPushException as e: # WebPushException got response object but we need to count # error points on subscriptions responses.append( (subscr, e.response) ) except Exception as e: exceptions.append( (subscr, e, time.time(),) ) return (responses, exceptions)
def send_push_notification(title, url, message): subs = PushNotification.objects.all() data = { "title": title, "url": url, "message": message, } # print(data) for s in subs: try: push = json.loads(s.subscription) subscription_info = { "endpoint": push['endpoint'], "keys": { "p256dh": push['keys']['p256dh'], "auth": push['keys']['auth'], }, } # webpush( # subscription_info, # data='hello', # vapid_private_key='gL1krMw2MRvwTGj8VS-UXZ9FdfOSvLf8nnl8EElGd-M', # vapid_claims={ # "sub": "mailto:[email protected]", # "aud": 'http://127.0.0.1:8000' # }, # ) wp = WebPusher(subscription_info) wp.send( 'hello', headers={ 'Authorization': 'WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAiLCJleHAiOiIxNTM1MTU3MjcxIiwic3ViIjoibWFpbHRvOmhlbGxvQHRoZWxpZmVzdHlsZXN0b3JlLmNvbS5uZyJ9.nNF82Zt_6K0f87nu6YgM5Fd_JpYGRvK-U554aHqNd8V_gS0d7gaWw9zEGVCZ66Rn-e28xtEkqBeD7GnZE0a3sg', 'Crypto-Key': 'p256ecdsa=BBVgtyidgfOeXent70LBOtWewZDouTMrfn_2eWBxV5AbpVjgpw9gQvgon07RmCJQ5iDOqn_8FrjmjR16pfn2GJU' }) except WebPushException as ex: print("Webpush not successful", ex) # Mozilla returns additional information in the body of the response. if ex.response and ex.response.json(): extra = ex.response.json() print("Remote service replied with a {}:{}, {}", extra.code, extra.errno, extra.message)
with open('config.json') as config_file: config = json.load(config_file) # Use grønstrøm API to get contents of push message message = requests.get('https://grønstrøm.nu/api/v1/next-day-short').text logging.info(f'Sending push message: {message}') # Get all subscriptions from SQLite database conn = sqlite3.Connection('/data/subs.db') try: c = conn.cursor() for i, row in enumerate(c.execute('SELECT * FROM subs')): try: # Manually recreate the push facade from the pywebpush API to be able to specify both TTL and urgency subscription_info = json.loads(row[0]) pusher = WebPusher(subscription_info) url = urlparse(subscription_info['endpoint']) aud = "{}://{}".format(url.scheme, url.netloc) vapid_claims = { 'sub': f'mailto:{config["sub_email"]}', 'aud': aud, 'exp': int(time.time()) + 12 * 60 * 60 } vv = Vapid.from_string(config['vapid_key']) headers = vv.sign(vapid_claims) # Define the urgency to be "normal", corresponding to messages being delivered # while the device is "On neither power nor wifi". # https://tools.ietf.org/html/draft-ietf-webpush-protocol-12#section-5.3 headers['Urgency'] = 'normal' resp = pusher.send(message, headers, ttl=12 * 60 * 60) # TODO: Handle cases where response status code is not 201.
def send_message(self, message="", **kwargs): """Send a message to a user.""" import jwt from pywebpush import WebPusher timestamp = int(time.time()) tag = str(uuid.uuid4()) payload = { 'badge': '/static/images/notification-badge.png', 'body': message, ATTR_DATA: {}, 'icon': '/static/icons/favicon-192x192.png', ATTR_TAG: tag, 'timestamp': (timestamp * 1000), # Javascript ms since epoch ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) } data = kwargs.get(ATTR_DATA) if data: # Pick out fields that should go into the notification directly vs # into the notification data dictionary. for key, val in data.copy().items(): if key in HTML5_SHOWNOTIFICATION_PARAMETERS: payload[key] = val del data[key] payload[ATTR_DATA] = data if (payload[ATTR_DATA].get(ATTR_URL) is None and payload.get(ATTR_ACTIONS) is None): payload[ATTR_DATA][ATTR_URL] = URL_ROOT targets = kwargs.get(ATTR_TARGET) if not targets: targets = self.registrations.keys() elif not isinstance(targets, list): targets = [targets] for target in targets: info = self.registrations.get(target) if info is None: _LOGGER.error( '%s is not a valid HTML5 push notification' ' target!', target) continue jwt_exp = (datetime.datetime.fromtimestamp(timestamp) + datetime.timedelta(days=JWT_VALID_DAYS)) jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] jwt_claims = { 'exp': jwt_exp, 'nbf': timestamp, 'iat': timestamp, ATTR_TARGET: target, ATTR_TAG: payload[ATTR_TAG] } jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8') payload[ATTR_DATA][ATTR_JWT] = jwt_token WebPusher(info[ATTR_SUBSCRIPTION]).send(json.dumps(payload), gcm_key=self._gcm_key, ttl='86400')
def send_message(self, message="", **kwargs): """Send a message to a user.""" import jwt from pywebpush import WebPusher timestamp = int(time.time()) tag = str(uuid.uuid4()) payload = { 'badge': '/static/images/notification-badge.png', 'body': message, ATTR_DATA: {}, 'icon': '/static/icons/favicon-192x192.png', ATTR_TAG: tag, 'timestamp': (timestamp*1000), # Javascript ms since epoch ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) } data = kwargs.get(ATTR_DATA) if data: # Pick out fields that should go into the notification directly vs # into the notification data dictionary. data_tmp = {} for key, val in data.items(): if key in HTML5_SHOWNOTIFICATION_PARAMETERS: payload[key] = val else: data_tmp[key] = val payload[ATTR_DATA] = data_tmp if (payload[ATTR_DATA].get(ATTR_URL) is None and payload.get(ATTR_ACTIONS) is None): payload[ATTR_DATA][ATTR_URL] = URL_ROOT targets = kwargs.get(ATTR_TARGET) if not targets: targets = self.registrations.keys() for target in list(targets): info = self.registrations.get(target) if info is None: _LOGGER.error("%s is not a valid HTML5 push notification" " target", target) continue jwt_exp = (datetime.datetime.fromtimestamp(timestamp) + datetime.timedelta(days=JWT_VALID_DAYS)) jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] jwt_claims = {'exp': jwt_exp, 'nbf': timestamp, 'iat': timestamp, ATTR_TARGET: target, ATTR_TAG: payload[ATTR_TAG]} jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8') payload[ATTR_DATA][ATTR_JWT] = jwt_token response = WebPusher(info[ATTR_SUBSCRIPTION]).send( json.dumps(payload), gcm_key=self._gcm_key, ttl='86400') # pylint: disable=no-member if response.status_code == 410: _LOGGER.info("Notification channel has expired") reg = self.registrations.pop(target) if not _save_config(self.registrations_json_path, self.registrations): self.registrations[target] = reg _LOGGER.error("Error saving registration.") else: _LOGGER.info("Configuration saved")
"p256dh": "BK7h-R0UgDeT89jhWi76-FlTtlEr3DbVBnrr34qmK91Husli_Fazu7vo7kW1mg9F_qhNzrs2glbrc6wfqGFsXks=", "auth": "CyOHiGNXPcT5Slo9UMx2uA==" } } data = 'aaaaaa' vapid_private_key = PRIVATE_KEY = ''' MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2xyYpqQhOaIdSbqH UwM3+ySvF47MoJyAFUNaHM7g/zOhRANCAAT554ztzCpjiIFOxNfEIicSzNPOZTIB Y1+CGl+LDfM5RlUNERFdfZYRqMmwvX7ydq7UiASkspWqdVVKZnLCzPD3 '''.strip() vapid_claims = {"sub": "mailto:[email protected]"} vapid_headers = None if vapid_claims: if not vapid_claims.get('aud'): url = urlparse(subscription_info.get('endpoint')) aud = "{}://{}".format(url.scheme, url.netloc) vapid_claims['aud'] = aud if os.path.isfile(vapid_private_key): vv = Vapid.from_file( private_key_file=vapid_private_key) # pragma no cover else: vv = Vapid.from_string(private_key=vapid_private_key) vapid_headers = vv.sign(vapid_claims) result = WebPusher(subscription_info, requests_session).send(data, vapid_headers) print result.text
def _push_message(self, payload, **kwargs): """Send the message.""" from pywebpush import WebPusher timestamp = int(time.time()) ttl = int(kwargs.get(ATTR_TTL, DEFAULT_TTL)) priority = kwargs.get(ATTR_PRIORITY, DEFAULT_PRIORITY) if priority not in ['normal', 'high']: priority = DEFAULT_PRIORITY payload['timestamp'] = (timestamp * 1000) # Javascript ms since epoch targets = kwargs.get(ATTR_TARGET) if not targets: targets = self.registrations.keys() for target in list(targets): info = self.registrations.get(target) try: info = REGISTER_SCHEMA(info) except vol.Invalid: _LOGGER.error( "%s is not a valid HTML5 push notification" " target", target) continue payload[ATTR_DATA][ATTR_JWT] = add_jwt( timestamp, target, payload[ATTR_TAG], info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH]) import jwt jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] jwt_exp = (datetime.fromtimestamp(timestamp) + timedelta(days=JWT_VALID_DAYS)) jwt_claims = { 'exp': jwt_exp, 'nbf': timestamp, 'iat': timestamp, ATTR_TARGET: target, ATTR_TAG: payload[ATTR_TAG] } jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8') payload[ATTR_DATA][ATTR_JWT] = jwt_token webpusher = WebPusher(info[ATTR_SUBSCRIPTION]) if self._vapid_prv and self._vapid_email: vapid_headers = create_vapid_headers(self._vapid_email, info[ATTR_SUBSCRIPTION], self._vapid_prv) vapid_headers.update({ 'urgency': priority, 'priority': priority }) response = webpusher.send(data=json.dumps(payload), headers=vapid_headers, ttl=ttl) else: # Only pass the gcm key if we're actually using GCM # If we don't, notifications break on FireFox gcm_key = self._gcm_key \ if 'googleapis.com' \ in info[ATTR_SUBSCRIPTION][ATTR_ENDPOINT] \ else None response = webpusher.send(json.dumps(payload), gcm_key=gcm_key, ttl=ttl) if response.status_code == 410: _LOGGER.info("Notification channel has expired") reg = self.registrations.pop(target) if not save_json(self.registrations_json_path, self.registrations): self.registrations[target] = reg _LOGGER.error("Error saving registration") else: _LOGGER.info("Configuration saved")
def get_webpusher(subscription): '''Return a WebPusher instance for a given subscription.''' return WebPusher(subscription)
def push(self, message: str, icon: str = '/static/favicon.ico') -> None: WebPusher(self.to_dict()).send( json.dumps({'body': message, 'icon': icon}), gcm_key=GOOGLE_API_KEY)
def test_encode_empty(self): subscription_info = self._gen_subscription_info() headers = {"Crypto-Key": "pre-existing", "Authentication": "bearer vapid"} encoded = WebPusher(subscription_info).encode('', headers) eq_(encoded, None)
for target in list(targets): info = self.registrations.get(target) try: info = REGISTER_SCHEMA(info) except vol.Invalid: _LOGGER.error( "%s is not a valid HTML5 push notification target", target) continue payload[ATTR_DATA][ATTR_JWT] = add_jwt( timestamp, target, payload[ATTR_TAG], info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH], ) webpusher = WebPusher(info[ATTR_SUBSCRIPTION]) if self._vapid_prv and self._vapid_email: vapid_headers = create_vapid_headers(self._vapid_email, info[ATTR_SUBSCRIPTION], self._vapid_prv) vapid_headers.update({ "urgency": priority, "priority": priority }) response = webpusher.send(data=json.dumps(payload), headers=vapid_headers, ttl=ttl) else: # Only pass the gcm key if we're actually using GCM # If we don't, notifications break on FireFox gcm_key = (self._gcm_key if "googleapis.com"
confirmation = raw_input("Would you like to send? (Y/n)") if confirmation.lower() != "y": print "Quitting...\n" exit() for token in tokens: subscription_info = { "endpoint": "https://android.googleapis.com/gcm/send/fuKkKlzSEEE:APA91bFKiuJ3LEx7Ke3xOEJ…Lx-t8INikH97ewASUn6OSzRAGeGf8Eu1B5Q7Lju_7QBj5VeGjwCePUufhiSzFXqEogaJ4esAqA", "keys": { "p256dh": token.p256dh, "auth": token.auth } } data = '{"data": {"message":"' + args.message + '","title":"' + args.title + '"}}' encoded = WebPusher(subscription_info).encode(data) crypto_key = "dh=" + encoded["crypto_key"] salt = "salt=" + encoded['salt'] headers = { 'Authorization': 'key=' + FCM_KEY, 'Content-Type': 'application/json', } headers.update({ 'crypto-key': crypto_key, 'content-encoding': 'aesgcm', 'encryption': salt }) fcm_data = { "raw_data": base64.b64encode(encoded.get('body')), "registration_ids": [token.endpoint[40:]] }