diff --git a/.travis.yml b/.travis.yml index d77bd09..bf689f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: -- '2.7' +- 2.7 +- 3.6 install: - make install - make install-dev @@ -15,6 +16,7 @@ deploy: password: secure: L+GM30lUbOih41dAXh0fD4d3L0pcYtnvLVL9XRM2TnXJvGhh1o/jcxbMilTH8PxyMRvan+52spoJn1zZHVuzTMfrtsENT60VsvV+1nyvev07w0JaaS1OBlgeiOVTJIk7O7+/B6Jeu48e/tZMX22cdw28/WK5gMUwnPycZIMyKG4= on: + python: 2.7 tags: true repo: ringcentral/ringcentral-python branch: master diff --git a/requirements.txt b/requirements.txt index 3489219..0886cbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ observable>=0.3.1 -pubnub==3.* +pubnub==4.* +pycryptodome>=3.4.4 requests>=2.12.4 \ No newline at end of file diff --git a/ringcentral/platform/events.py b/ringcentral/platform/events.py new file mode 100644 index 0000000..15fe593 --- /dev/null +++ b/ringcentral/platform/events.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +class Events: + refreshSuccess = 'refreshSuccess' + refreshError = 'refreshError' + loginSuccess = 'loginSuccess' + loginError = 'loginError' + logoutSuccess = 'logoutSuccess' + logoutError = 'logoutError' + + def __init__(self): + pass diff --git a/ringcentral/platform/platform.py b/ringcentral/platform/platform.py index aae8794..487ea8b 100644 --- a/ringcentral/platform/platform.py +++ b/ringcentral/platform/platform.py @@ -2,7 +2,9 @@ # encoding: utf-8 import sys +from observable import Observable from .auth import Auth +from .events import Events from ..core import base64encode ACCOUNT_ID = '~' @@ -15,8 +17,11 @@ REFRESH_TOKEN_TTL = 604800 # 1 week -class Platform: +class Platform(Observable): def __init__(self, client, key='', secret='', server='', name='', version=''): + + Observable.__init__(self) + self._server = server self._key = key self._name = name if name else 'Unnamed' @@ -63,42 +68,50 @@ def logged_in(self): return False def login(self, username, extension, password): - response = self._request_token(TOKEN_ENDPOINT, body={ - 'grant_type': 'password', - 'username': username, - 'extension': extension, - 'password': password, - 'access_token_ttl': ACCESS_TOKEN_TTL, - 'refresh_token_ttl': REFRESH_TOKEN_TTL - }) - self._auth.set_data(response.json_dict()) - # TODO Add event trigger - return response + try: + response = self._request_token(TOKEN_ENDPOINT, body={ + 'grant_type': 'password', + 'username': username, + 'extension': extension, + 'password': password, + 'access_token_ttl': ACCESS_TOKEN_TTL, + 'refresh_token_ttl': REFRESH_TOKEN_TTL + }) + self._auth.set_data(response.json_dict()) + self.trigger(Events.loginSuccess, response) + return response + except Exception as e: + self.trigger(Events.loginError, e) + raise e def refresh(self): - if not self._auth.refresh_token_valid(): - raise Exception('Refresh token has expired') - - response = self._request_token(TOKEN_ENDPOINT, body={ - 'grant_type': 'refresh_token', - 'refresh_token': self._auth.refresh_token(), - 'access_token_ttl': ACCESS_TOKEN_TTL, - 'refresh_token_ttl': REFRESH_TOKEN_TTL - }) - - self._auth.set_data(response.json_dict()) - - # TODO Add event trigger - - return response + try: + if not self._auth.refresh_token_valid(): + raise Exception('Refresh token has expired') + response = self._request_token(TOKEN_ENDPOINT, body={ + 'grant_type': 'refresh_token', + 'refresh_token': self._auth.refresh_token(), + 'access_token_ttl': ACCESS_TOKEN_TTL, + 'refresh_token_ttl': REFRESH_TOKEN_TTL + }) + self._auth.set_data(response.json_dict()) + self.trigger(Events.refreshSuccess, response) + return response + except Exception as e: + self.trigger(Events.refreshError, e) + raise e def logout(self): - response = self._request_token(REVOKE_ENDPOINT, body={ - 'token': self._auth.access_token() - }) - self._auth.reset() - # TODO Add event trigger - return response + try: + response = self._request_token(REVOKE_ENDPOINT, body={ + 'token': self._auth.access_token() + }) + self._auth.reset() + self.trigger(Events.logoutSuccess, response) + return response + except Exception as e: + self.trigger(Events.logoutError, e) + raise e def inflate_request(self, request, skip_auth_check=False): if not skip_auth_check: diff --git a/ringcentral/subscription/events.py b/ringcentral/subscription/events.py index 9c0a8d1..d055a7d 100644 --- a/ringcentral/subscription/events.py +++ b/ringcentral/subscription/events.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # encoding: utf-8 + class Events: connectionError = 'connectionError' notification = 'notification' diff --git a/ringcentral/subscription/subscription.py b/ringcentral/subscription/subscription.py index 0fd3ea0..75e2d5e 100644 --- a/ringcentral/subscription/subscription.py +++ b/ringcentral/subscription/subscription.py @@ -148,34 +148,37 @@ def _subscribe_at_pubnub(self): if not self.alive(): raise Exception('Subscription is not alive') - from pubnub import Pubnub - - s_key = self._subscription['deliveryMode']['subscriberKey'] - self._pubnub = Pubnub(subscribe_key=s_key, ssl_on=False, publish_key='') - - def callback(message, channel=''): - self._notify(message) - - def error(message): - self.trigger(Events.connectionError, message) - - def connect(message): - pass - - def reconnect(message): - pass - - def disconnect(message): - pass - - self._pubnub.subscribe( - self._subscription['deliveryMode']['address'], - callback=callback, - error=error, - connect=connect, - reconnect=reconnect, - disconnect=disconnect - ) + from pubnub.pubnub import PubNub + from pubnub.pnconfiguration import PNConfiguration + from pubnub.callbacks import SubscribeCallback + from pubnub.enums import PNStatusCategory + + pnconf = PNConfiguration() + pnconf.subscribe_key = self._subscription['deliveryMode']['subscriberKey'] + self._pubnub = PubNub(pnconf) + + subscription = self + + class SubscribeCallbackImpl(SubscribeCallback): + def presence(self, pubnub, presence): + pass # handle incoming presence data + + def status(self, pubnub, status): + if status.category == PNStatusCategory.PNUnexpectedDisconnectCategory: + subscription.trigger(Events.connectionError, 'Connectivity loas') + pass + elif status.category == PNStatusCategory.PNConnectedCategory: + pass + elif status.category == PNStatusCategory.PNReconnectedCategory: + pass + elif status.category == PNStatusCategory.PNDecryptionErrorCategory: + pass + + def message(self, pubnub, pnmessage): # instance of PNMessageResult + subscription._notify(pnmessage.message) + + self._pubnub.add_listener(SubscribeCallbackImpl()) + self._pubnub.subscribe().channels(self._subscription['deliveryMode']['address']).execute() def _notify(self, message): message = self._decrypt(message) @@ -185,7 +188,7 @@ def _decrypt(self, message): if not self.alive(): raise Exception('Subscription is not alive') - from pubnub import AES + from Crypto.Cipher import AES delivery_mode = self._subscription['deliveryMode'] is_encrypted = ('encryption' in delivery_mode) and ('encryptionKey' in delivery_mode) @@ -201,7 +204,7 @@ def _decrypt(self, message): def _unsubscribe_at_pubnub(self): if self._pubnub and self.alive(): - self._pubnub.unsubscribe(self._subscription['deliveryMode']['address']) + self._pubnub.unsubscribe().channels(self._subscription['deliveryMode']['address']).execute() def _get_full_events_filter(self): return [self._platform.create_url(e) for e in self._event_filters] diff --git a/ringcentral/test/testcase.py b/ringcentral/test/testcase.py index 1b3b4ff..f24ae77 100644 --- a/ringcentral/test/testcase.py +++ b/ringcentral/test/testcase.py @@ -31,6 +31,14 @@ def get_sdk(self, mock): text='' ) + matcher = re.compile('ps\.pndsn\.com') + + mock.register_uri( + method=requests_mock.ANY, + url=matcher, + text='' + ) + return sdk def add(self, mock, method, url, body, status=200): diff --git a/setup.py b/setup.py index 7794d30..c7b66bb 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from distutils.core import setup -VERSION = '0.7.4' +VERSION = '0.7.5' setup( name='ringcentral', @@ -20,7 +20,8 @@ keywords=['sdk', 'ringcentral', 'connect', 'platform', 'api', 'python'], install_requires=[ 'observable>=0.3.1', - 'pubnub==3.*', + 'pubnub==4.*', + 'pycryptodome>=3.4.4', 'requests>=2.7.0' ], classifiers=[]