Improve code quality
This commit is contained in:
+8
-7
@@ -14,6 +14,7 @@ class NotificationPriority(Enum):
|
|||||||
Immediate = '10'
|
Immediate = '10'
|
||||||
Delayed = '5'
|
Delayed = '5'
|
||||||
|
|
||||||
|
|
||||||
RequestStream = collections.namedtuple('RequestStream', ['stream_id', 'token'])
|
RequestStream = collections.namedtuple('RequestStream', ['stream_id', 'token'])
|
||||||
Notification = collections.namedtuple('Notification', ['token', 'payload'])
|
Notification = collections.namedtuple('Notification', ['token', 'payload'])
|
||||||
|
|
||||||
@@ -31,7 +32,8 @@ class APNsClient(object):
|
|||||||
DEFAULT_PORT = 443
|
DEFAULT_PORT = 443
|
||||||
ALTERNATIVE_PORT = 2197
|
ALTERNATIVE_PORT = 2197
|
||||||
|
|
||||||
def __init__(self, credentials, use_sandbox=False, use_alternative_port=False, proto=None, json_encoder=None, password=None):
|
def __init__(self, credentials, use_sandbox=False, use_alternative_port=False, proto=None, json_encoder=None,
|
||||||
|
password=None):
|
||||||
if credentials is None or isinstance(credentials, str):
|
if credentials is None or isinstance(credentials, str):
|
||||||
self.__credentials = CertificateCredentials(credentials, password)
|
self.__credentials = CertificateCredentials(credentials, password)
|
||||||
else:
|
else:
|
||||||
@@ -45,8 +47,7 @@ class APNsClient(object):
|
|||||||
def _init_connection(self, use_sandbox, use_alternative_port, proto):
|
def _init_connection(self, use_sandbox, use_alternative_port, proto):
|
||||||
server = self.SANDBOX_SERVER if use_sandbox else self.LIVE_SERVER
|
server = self.SANDBOX_SERVER if use_sandbox else self.LIVE_SERVER
|
||||||
port = self.ALTERNATIVE_PORT if use_alternative_port else self.DEFAULT_PORT
|
port = self.ALTERNATIVE_PORT if use_alternative_port else self.DEFAULT_PORT
|
||||||
self._connection = self.__credentials.create_connection(server, port,
|
self._connection = self.__credentials.create_connection(server, port, proto)
|
||||||
proto)
|
|
||||||
|
|
||||||
def send_notification(self, token_hex, notification, topic=None, priority=NotificationPriority.Immediate,
|
def send_notification(self, token_hex, notification, topic=None, priority=NotificationPriority.Immediate,
|
||||||
expiration=None, collapse_id=None):
|
expiration=None, collapse_id=None):
|
||||||
@@ -92,7 +93,7 @@ class APNsClient(object):
|
|||||||
|
|
||||||
def send_notification_batch(self, notifications, topic=None, priority=NotificationPriority.Immediate,
|
def send_notification_batch(self, notifications, topic=None, priority=NotificationPriority.Immediate,
|
||||||
expiration=None, collapse_id=None):
|
expiration=None, collapse_id=None):
|
||||||
'''
|
"""
|
||||||
Send a notification to a list of tokens in batch. Instead of sending a synchronous request
|
Send a notification to a list of tokens in batch. Instead of sending a synchronous request
|
||||||
for each token, send multiple requests concurrently. This is done on the same connection,
|
for each token, send multiple requests concurrently. This is done on the same connection,
|
||||||
using HTTP/2 streams (one request per stream).
|
using HTTP/2 streams (one request per stream).
|
||||||
@@ -104,7 +105,7 @@ class APNsClient(object):
|
|||||||
The function returns a dictionary mapping each token to its result. The result is "Success"
|
The function returns a dictionary mapping each token to its result. The result is "Success"
|
||||||
if the token was sent successfully, or the string returned by APNs in the 'reason' field of
|
if the token was sent successfully, or the string returned by APNs in the 'reason' field of
|
||||||
the response, if the token generated an error.
|
the response, if the token generated an error.
|
||||||
'''
|
"""
|
||||||
notification_iterator = iter(notifications)
|
notification_iterator = iter(notifications)
|
||||||
next_notification = next(notification_iterator, None)
|
next_notification = next(notification_iterator, None)
|
||||||
# Make sure we're connected to APNs, so that we receive and process the server's SETTINGS
|
# Make sure we're connected to APNs, so that we receive and process the server's SETTINGS
|
||||||
@@ -171,10 +172,10 @@ class APNsClient(object):
|
|||||||
self.__max_concurrent_streams = max_concurrent_streams
|
self.__max_concurrent_streams = max_concurrent_streams
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
'''
|
"""
|
||||||
Establish a connection to APNs. If already connected, the function does nothing. If the
|
Establish a connection to APNs. If already connected, the function does nothing. If the
|
||||||
connection fails, the function retries up to MAX_CONNECTION_RETRIES times.
|
connection fails, the function retries up to MAX_CONNECTION_RETRIES times.
|
||||||
'''
|
"""
|
||||||
retries = 0
|
retries = 0
|
||||||
while retries < MAX_CONNECTION_RETRIES:
|
while retries < MAX_CONNECTION_RETRIES:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+35
-41
@@ -1,29 +1,22 @@
|
|||||||
|
import time
|
||||||
|
import jwt
|
||||||
|
|
||||||
from hyper import HTTP20Connection
|
from hyper import HTTP20Connection
|
||||||
from hyper.tls import init_context
|
from hyper.tls import init_context
|
||||||
|
|
||||||
import jwt
|
|
||||||
|
|
||||||
# For creating and comparing the time for the JWT token
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TOKEN_LIFETIME = 3600
|
DEFAULT_TOKEN_LIFETIME = 3600
|
||||||
DEFAULT_TOKEN_ENCRYPTION_ALGORITHM = 'ES256'
|
DEFAULT_TOKEN_ENCRYPTION_ALGORITHM = 'ES256'
|
||||||
|
|
||||||
|
|
||||||
# Abstract Base class. This should not be instantiated directly.
|
# Abstract Base class. This should not be instantiated directly.
|
||||||
class Credentials(object):
|
class Credentials(object):
|
||||||
|
|
||||||
def __init__(self, ssl_context=None):
|
def __init__(self, ssl_context=None):
|
||||||
self.__ssl_context = ssl_context
|
self.__ssl_context = ssl_context
|
||||||
|
|
||||||
# Creates a connection with the credentials, if available or necessary.
|
# Creates a connection with the credentials, if available or necessary.
|
||||||
def create_connection(self, server, port, proto):
|
def create_connection(self, server, port, proto):
|
||||||
# self.__ssl_context may be none, and that's fine.
|
# self.__ssl_context may be none, and that's fine.
|
||||||
return HTTP20Connection(server, port,
|
return HTTP20Connection(server, port, ssl_context=self.__ssl_context, force_proto=proto or 'h2')
|
||||||
ssl_context=self.__ssl_context,
|
|
||||||
force_proto=proto or 'h2')
|
|
||||||
|
|
||||||
def get_authorization_header(self, topic):
|
def get_authorization_header(self, topic):
|
||||||
return None
|
return None
|
||||||
@@ -41,15 +34,13 @@ class CertificateCredentials(Credentials):
|
|||||||
# Credentials subclass for JWT token based authentication
|
# Credentials subclass for JWT token based authentication
|
||||||
class TokenCredentials(Credentials):
|
class TokenCredentials(Credentials):
|
||||||
def __init__(self, auth_key_path, auth_key_id, team_id,
|
def __init__(self, auth_key_path, auth_key_id, team_id,
|
||||||
encryption_algorithm=None, token_lifetime=None):
|
encryption_algorithm=DEFAULT_TOKEN_ENCRYPTION_ALGORITHM,
|
||||||
|
token_lifetime=DEFAULT_TOKEN_LIFETIME):
|
||||||
self.__auth_key = self._get_signing_key(auth_key_path)
|
self.__auth_key = self._get_signing_key(auth_key_path)
|
||||||
self.__auth_key_id = auth_key_id
|
self.__auth_key_id = auth_key_id
|
||||||
self.__team_id = team_id
|
self.__team_id = team_id
|
||||||
self.__encryption_algorithm = DEFAULT_TOKEN_ENCRYPTION_ALGORITHM if \
|
self.__encryption_algorithm = encryption_algorithm
|
||||||
encryption_algorithm is None else \
|
self.__token_lifetime = token_lifetime
|
||||||
encryption_algorithm
|
|
||||||
self.__token_lifetime = DEFAULT_TOKEN_LIFETIME if \
|
|
||||||
token_lifetime is None else token_lifetime
|
|
||||||
|
|
||||||
# Dictionary of {topic: (issue time, ascii decoded token)}
|
# Dictionary of {topic: (issue time, ascii decoded token)}
|
||||||
self.__topicTokens = {}
|
self.__topicTokens = {}
|
||||||
@@ -64,34 +55,37 @@ class TokenCredentials(Credentials):
|
|||||||
token = self._get_or_create_topic_token(topic)
|
token = self._get_or_create_topic_token(topic)
|
||||||
return 'bearer %s' % token
|
return 'bearer %s' % token
|
||||||
|
|
||||||
def _isExpiredToken(self, issueDate):
|
@staticmethod
|
||||||
now = time.time()
|
def _is_expired_token(issue_date):
|
||||||
return now < issueDate + DEFAULT_TOKEN_LIFETIME
|
return time.time() < issue_date + DEFAULT_TOKEN_LIFETIME
|
||||||
|
|
||||||
def _get_or_create_topic_token(self, topic):
|
@staticmethod
|
||||||
# dict of topic to issue date and JWT token
|
def _get_signing_key(key_path):
|
||||||
tokenPair = self.__topicTokens.get(topic)
|
|
||||||
if tokenPair is None or self._isExpiredToken(tokenPair[0]):
|
|
||||||
# Create a new token
|
|
||||||
issuedAt = time.time()
|
|
||||||
tokenDict = {'iss': self.__team_id,
|
|
||||||
'iat': issuedAt
|
|
||||||
}
|
|
||||||
headers = {'alg': self.__encryption_algorithm,
|
|
||||||
'kid': self.__auth_key_id,
|
|
||||||
}
|
|
||||||
jwtToken = jwt.encode(tokenDict, self.__auth_key,
|
|
||||||
algorithm=self.__encryption_algorithm,
|
|
||||||
headers=headers).decode('ascii')
|
|
||||||
|
|
||||||
self.__topicTokens[topic] = (issuedAt, jwtToken)
|
|
||||||
return jwtToken
|
|
||||||
else:
|
|
||||||
return tokenPair[1]
|
|
||||||
|
|
||||||
def _get_signing_key(self, key_path):
|
|
||||||
secret = ''
|
secret = ''
|
||||||
if key_path:
|
if key_path:
|
||||||
with open(key_path) as f:
|
with open(key_path) as f:
|
||||||
secret = f.read()
|
secret = f.read()
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
|
def _get_or_create_topic_token(self, topic):
|
||||||
|
# dict of topic to issue date and JWT token
|
||||||
|
token_pair = self.__topicTokens.get(topic)
|
||||||
|
if token_pair is None or self._is_expired_token(token_pair[0]):
|
||||||
|
# Create a new token
|
||||||
|
issued_at = time.time()
|
||||||
|
token_dict = {
|
||||||
|
'iss': self.__team_id,
|
||||||
|
'iat': issued_at,
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'alg': self.__encryption_algorithm,
|
||||||
|
'kid': self.__auth_key_id,
|
||||||
|
}
|
||||||
|
jwt_token = jwt.encode(token_dict, self.__auth_key,
|
||||||
|
algorithm=self.__encryption_algorithm,
|
||||||
|
headers=headers).decode('ascii')
|
||||||
|
|
||||||
|
self.__topicTokens[topic] = (issued_at, jwt_token)
|
||||||
|
return jwt_token
|
||||||
|
else:
|
||||||
|
return token_pair[1]
|
||||||
|
|||||||
+1
-1
@@ -110,7 +110,7 @@ class InvalidProviderToken(APNsException):
|
|||||||
|
|
||||||
|
|
||||||
class MissingProviderToken(APNsException):
|
class MissingProviderToken(APNsException):
|
||||||
"""No provider certificate was used to connect to APNs and Authorization header was missing or no provider token
|
"""No provider certificate was used to connect to APNs and Authorization header was missing or no provider token
|
||||||
was specified. """
|
was specified. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user