Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F7159836
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/pushserver/applications/sylk.py b/pushserver/applications/sylk.py
index dfa88d2..a5eba01 100644
--- a/pushserver/applications/sylk.py
+++ b/pushserver/applications/sylk.py
@@ -1,159 +1,159 @@
import datetime
from pushserver.applications.apple import *
from pushserver.applications.firebase import *
from pushserver.resources.utils import callid_to_uuid
-from firebase_admin import messaging
+#from firebase_admin import messaging
__all__ = ['AppleSylkHeaders', 'AppleSylkPayload',
'FirebaseSylkHeaders', 'FirebaseSylkPayload']
class AppleSylkHeaders(AppleHeaders):
"""
An Apple headers structure for a push notification
"""
def create_push_type(self) -> str:
"""
logic to define apns_push_type value using request parameters
apns_push_type reflect the contents of the notification’s payload,
it can be:
'alert', 'background', 'voip',
'complication', 'fileprovider' or 'mdm'.
"""
push_type = 'voip' if self.event in ('incoming_session', 'incoming_conference_request') else 'background'
return push_type
def create_expiration(self) -> int:
"""
logic to define apns_expiration value using request parameters
apns_expiration is the date at which the notification expires,
(UNIX epoch expressed in seconds UTC).
"""
return '120'
def create_topic(self) -> str:
"""
logic to define apns_topic value using request parameters
apns_topic is in general is the app’s bundle ID and may have
a suffix based on the notification’s type.
"""
apns_topic = self.app_id
if self.app_id.endswith('.dev') or self.app_id.endswith('.prod'):
apns_topic = '.'.join(self.app_id.split('.')[:-1])
if self.event in ('incoming_session', 'incoming_conference_request'):
apns_topic = f"{apns_topic}.voip"
return apns_topic
def create_priority(self) -> int:
"""
logic to define apns_priority value using request parameters
Notification priority,
apns_prioriy 10 o send the notification immediately,
5 to send the notification based on power considerations
on the user’s device.
"""
apns_priority = '10' if self.event in ('incoming_session', 'incoming_conference_request') else '5'
return apns_priority
class FirebaseSylkHeaders(FirebaseHeaders):
"""
Firebase headers for a push notification
"""
class AppleSylkPayload(ApplePayload):
"""
A payload for a Apple Sylk push notification
"""
@property
def payload(self) -> str:
"""
Generate an AppleSylk notification payload
"""
if self.event == 'cancel':
payload = {
'event': self.event,
'call-id': self.call_id,
'session-id': callid_to_uuid(self.call_id),
'reason': self.reason
}
else:
payload = {
'event': self.event,
'call-id': self.call_id,
'session-id': callid_to_uuid(self.call_id),
'media-type': self.media_type,
'from_uri': self.sip_from,
'from_display_name': self.from_display_name,
'to_uri': self.sip_to
}
return payload
class FirebaseSylkPayload(FirebasePayload):
"""
A payload for a Firebase Sylk push notification
"""
@property
def payload(self) -> str:
"""
Generate a Sylk payload and extra Firebase parameters
"""
if not self.from_display_name:
from_display_name = self.sip_from
else:
from_display_name = self.from_display_name
if self.event == 'cancel':
data = {
'event': self.event,
'call-id': self.call_id,
'session-id': callid_to_uuid(self.call_id),
'reason': self.reason
}
else:
data = {
'event': self.event,
'call-id': self.call_id,
'session-id': callid_to_uuid(self.call_id),
'media-type': self.media_type,
'from_uri': self.sip_from,
'from_display_name': from_display_name,
'to_uri': self.sip_to
}
http_payload = {
'message': {
'token': self.token,
'data': data,
'android': {
'priority': 'high',
'ttl': '60s'
}
}
}
- fcm_payload = messaging.Message(
- token=self.token,
- data=data,
- android=messaging.AndroidConfig(
- ttl=datetime.timedelta(seconds=60),
- priority='high'
- )
- )
+ #fcm_payload = messaging.Message(
+ # token=self.token,
+ # data=data,
+ # android=messaging.AndroidConfig(
+ # ttl=datetime.timedelta(seconds=60),
+ # priority='high'
+ # )
+ #)
return http_payload
diff --git a/pushserver/pns/firebase.py b/pushserver/pns/firebase.py
index 355905f..7e0f3ec 100644
--- a/pushserver/pns/firebase.py
+++ b/pushserver/pns/firebase.py
@@ -1,406 +1,405 @@
import json
import os
import time
from datetime import datetime
import oauth2client
import requests
from pushserver.models.requests import WakeUpRequest
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from pushserver.pns.base import PNS, PushRequest, PlatformRegister
from pushserver.resources.utils import log_event, fix_non_serializable_types
-import firebase_admin
-from firebase_admin import messaging
-
-default_app = firebase_admin.initialize_app()
+#import firebase_admin
+#from firebase_admin import messaging
+#default_app = firebase_admin.initialize_app()
class FirebasePNS(PNS):
"""
A Firebase Push Notification service
"""
def __init__(self, app_id: str, app_name: str, url_push: str,
voip: bool, auth_key: str = None, auth_file: str = None):
"""
:param app_id `str`: Application ID.
:param url_push `str`: URI to push a notification.
:param voip `bool`: required for apple, `True` for voip push notification type.
:param auth_key `str`: A Firebase credential for push notifications.
:param auth_file `str`: A Firebase credential for push notifications.
"""
self.app_id = app_id
self.app_name = app_name
self.url_push = url_push
self.voip = voip
self.auth_key = auth_key
self.auth_file = auth_file
self.error = ''
class FirebaseRegister(PlatformRegister):
def __init__(self, app_id: str, app_name: str, voip: bool,
config_dict: dict, credentials_path: str, loggers: dict):
self.app_id = app_id
self.app_name = app_name
self.voip = voip
self.credentials_path = credentials_path
self.config_dict = config_dict
self.loggers = loggers
self.error = ''
self.auth_key, self.auth_file = self.set_auths()
@property
def url_push(self):
try:
return self.config_dict['firebase_push_url']
except KeyError:
self.error = 'firebase_push_url not found in applications.ini'
return None
def set_auths(self):
auth_key = None
auth_file = None
try:
auth_key = self.config_dict['firebase_authorization_key']
except KeyError:
try:
auth_file = self.config_dict['firebase_authorization_file']
if self.credentials_path:
auth_file = f"{self.credentials_path}/" \
f"{auth_file}"
else:
pass
if not os.path.exists(auth_file):
self.error = f'{auth_file} - no such file'
except KeyError:
self.error = 'not firebase_authorization_key or ' \
'firebase_authorization_file found in applications.ini'
return auth_key, auth_file
@property
def pns(self) -> FirebasePNS:
pns = None
if self.auth_key:
auth_file = ''
pns = FirebasePNS(app_id=self.app_id,
app_name=self.app_name,
url_push=self.url_push,
voip=self.voip,
auth_key=self.auth_key,
auth_file=auth_file)
elif self.auth_file:
pns = FirebasePNS(app_id=self.app_id,
app_name=self.app_name,
url_push=self.url_push,
voip=self.voip,
auth_file=self.auth_file)
self.error = pns.error if pns.error else ''
return pns
@property
def register_entries(self):
if self.error:
return {}
return {'pns': self.pns,
'auth_key': self.auth_key,
'auth_file': self.auth_file,
'refreshed_token': False}
class FirebasePushRequest(PushRequest):
"""
Firebase push notification request
"""
def __init__(self, error: str, app_name: str, app_id: str,
request_id: str, headers: str, payload: dict,
loggers: dict, log_remote: dict,
wp_request: WakeUpRequest, register: dict):
"""
:param error: `str`
:param app_name: `str` 'linphone' or 'payload'
:param app_id: `str` bundle id
:param headers: `FirebaseHeaders` Firebase push notification headers
:param payload: `FirebasePayload`Firebase push notification payload
:param wp_request: `WakeUpRequest`
:param loggers: `dict` global logging instances to write messages (params.loggers)
"""
self.error = error
self.app_name = app_name
self.app_id = app_id
self.platform = 'firebase'
self.request_id = request_id
self.headers = headers
self.payload = payload
self.token = wp_request.token
self.wp_request = wp_request
self.loggers = loggers
self.log_remote = log_remote
self.pns = register['pns']
self.path = self.pns.url_push
self.results = self.send_http_notification()
# self.results = self.send_fcm_notification()
def requests_retry_session(self, counter=0):
"""
Define parameters to retry a push notification
according to media_type.
:param counter: `int` (optional) if retries was necessary
because of connection fails
Following rfc3261 specification, an exponential backoff factor is used.
More specifically:
backoff = 0.5
T1 = 500ms
max_retries_call = 7
time_to_live_call = 64 seconds
max_retries_sms = 11
time_to_live_sms ~ 2 hours
"""
retries = self.retries_params[self.media_type] - counter
backoff_factor = self.retries_params['bo_factor'] * 0.5 * 2 ** counter
status_forcelist = tuple([status for status in range(500, 600)])
session = None
session = session or requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def send_http_notification(self) -> dict:
"""
Send a Firebase push notification over HTTP
"""
if self.error:
self.log_error()
return {'code': 500, 'body': {}, 'reason': 'Internal server error'}
n_retries, backoff_factor = self.retries_params(self.wp_request.media_type)
counter = 0
error = False
code = 500
reason = ""
body = None
response = None
while counter <= n_retries:
self.log_request(path=self.pns.url_push)
try:
response = requests.post(self.pns.url_push,
self.payload,
headers=self.headers)
break
except requests.exceptions.RequestException as e:
error = True
reason = f'connection failed: {e}'
counter += 1
timer = backoff_factor * (2 ** (counter - 1))
time.sleep(timer)
if counter == n_retries:
reason = "maximum retries reached"
elif error:
try:
response = self.requests_retry_session(counter). \
post(self.pns.url_push,
self.payload,
headers=self.headers)
except Exception as x:
level = 'error'
msg = f"outgoing {self.platform.title()} response for " \
f"{self.request_id}, push failed: " \
f"an error occurred in {x.__class__.__name__}"
log_event(loggers=self.loggers, msg=msg, level=level)
try:
body = response.__dict__
except (TypeError, ValueError):
code = 500
reason = 'cannot parse response body'
body = {}
else:
reason = body.get('reason')
code = response.status_code
for k in ('raw', 'request', 'connection', 'cookies', 'elapsed'):
try:
del body[k]
except KeyError:
pass
except TypeError:
break
body = json.dumps(fix_non_serializable_types(body))
if isinstance(body, str):
body = json.loads(body)
if code == 200:
description = 'OK'
try:
failure = body['_content']['failure']
except KeyError:
pass
else:
if failure == 1:
description = body['_content']['results'][0]['error']
code = 410
else:
try:
reason = body['reason']
except KeyError:
reason = None
try:
details = body['_content']['error']['message']
except KeyError:
details = None
try:
internal_code = body['_content']['error']['code']
except KeyError:
internal_code = None
if internal_code == 400 and 'not a valid FCM registration token' in details:
code = 410
elif internal_code == 404:
code = 410
if reason and details:
description = "%s %s" % (reason, details)
elif reason:
description = reason
elif details:
error_description = details
else:
description = 'unknown failure reason'
keys = list(body.keys())
for key in keys:
if not body[key]:
del body[key]
results = {'body': body,
'code': code,
'reason': description,
'url': self.pns.url_push,
'platform': 'firebase',
'call_id': self.wp_request.call_id,
'token': self.token
}
self.results = results
self.log_results()
return results
def send_fcm_notification(self) -> dict:
"""
Send a native Firebase push notification
"""
if self.error:
self.log_error()
return {'code': 500, 'body': {}, 'reason': 'Internal server error'}
n_retries, backoff_factor = self.retries_params(self.wp_request.media_type)
counter = 0
error = False
code = 200
response = None
body = None
reason = None
while counter <= n_retries:
self.log_request(path=self.pns.url_push)
try:
response = messaging.send(self.payload['fcm'])
break
except Exception as e:
error = True
response = f'connection failed: {e}'
counter += 1
timer = backoff_factor * (2 ** (counter - 1))
conde = 500
time.sleep(timer)
if counter == n_retries:
reason = "maximum retries reached"
elif error:
try:
response = self.requests_retry_session(counter). \
post(self.pns.url_push,
self.payload,
headers=self.headers)
except Exception as x:
level = 'error'
msg = f"outgoing {self.platform.title()} response for " \
f"{self.request_id}, push failed: " \
f"an error occurred in {x.__class__.__name__}"
log_event(loggers=self.loggers, msg=msg, level=level)
body = {'response': response}
results = {'body': body,
'code': code,
'reason': reason,
'url': self.pns.url_push,
'platform': 'firebase',
'call_id': self.wp_request.call_id,
'token': self.token
}
self.results = results
self.log_results()
# Request is missing required authentication credential.
# Expected OAuth 2 access token, login cookie or other valid authentication
# credential. UNAUTHENTICATED
code = results.get('code')
reason = results.get('reason')
if not got401 and code == 401 and reason == 'Unauthorized':
if not self.pns.get('refreshed_token'):
level = 'warn'
msg = f"outgoing {self.platform.title()} response for request " \
f"{self.request_id} need a new access token - " \
f"server will refresh it and try again"
log_event(loggers=self.loggers, msg=msg, level=level, to_file=True)
# retry with a new Fireplace access token
self.pns.access_token = self.pns.set_access_token()
level = 'warn'
msg = f"outgoing {self.platform.title()} response for request " \
f"{self.request_id} a new access token was generated - " \
f"trying again"
log_event(loggers=self.loggers, msg=msg, level=level, to_file=True)
self.results = self.send_notification()
return results
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 2:04 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3409196
Default Alt Text
(19 KB)
Attached To
Mode
rSYLKPUSH Sylk Pushserver
Attached
Detach File
Event Timeline
Log In to Comment