Esempio n. 1
0
def send_notification(apns_token, message, sender, channel, badge=1, network=None, intent=None, private=False):
    global apns_client

    if apns_client is None:
        apns_client = APNsClient(os.path.join(DIRECTORY, 'production.pem'), heartbeat_period=30)

    query = None

    if channel:
        query = channel
    elif sender:
        query = sender

    if message and intent == 'ACTION':
        message = '* %s' % (message)

    sound = None
    alert = {}

    if message or private:
        sound = 'default'

    user_info = {}

    if query and network:
        user_info['n'] = network
        user_info['q'] = query

    if sender:
        alert['title'] = sender

    if channel:
        alert['subtitle'] = channel

    if private:
        alert['loc-key'] = 'INPUT_MESSAGE_PLACEHOLDER'
        alert['body'] = 'Message'
    elif message:
        alert['body'] = message

    payload = Payload(alert=alert, sound=sound, badge=badge, custom=user_info)

    apns_client.connect()

    try:
        apns_client.send_notification(apns_token, payload, TOPIC)
    except (BadDeviceToken, Unregistered) as e:
        with database.transaction():
            try:
                device = Device.get(Device.apns_token == apns_token)
                device.delete_instance(recursive=True)
            except Device.DoesNotExist:
                return
Esempio n. 2
0
def get_apns2_connection(app, device, unique_key):
    """
    Get the active APNSv2 connection.

    This returns a reference to the global connection object,
    and initializes it if the connection was not yet made.

    Args:
        app (App): App requesting the connection.
        device (Device): Device requesting the connection.
        unique_key (str): Unique key used for logging.

    Returns:
        APNsClient.
    """
    global apns2_connection_pool
    if app.app_id not in apns2_connection_pool:
        full_cert_path = os.path.join(settings.CERT_DIR, app.push_key)
        apns2_connection = APNsClient(full_cert_path,
                                      use_sandbox=settings.APNS_IS_SANDBOX)
        log_middleware_information(
            '{0} | Opened new connection to APNSv2 for app_id: {1}',
            OrderedDict([
                ('token', unique_key),
                ('app_id', app.app_id),
            ]),
            logging.INFO,
            device=device,
        )
        apns2_connection_pool.update({app.app_id: apns2_connection})
    else:
        # Test the existing connection, will throw an exception if this fails.
        apns2_connection = apns2_connection_pool[app.app_id]
        apns2_connection.connect()

    return apns2_connection
Esempio n. 3
0
class ClientTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        # Ignore all log messages so that test output is not cluttered.
        logging.basicConfig(level=logging.CRITICAL)
        cls.tokens = ['%064x' % i for i in range(10000)]
        cls.payload = Payload(alert='Test alert')
        cls.notifications = [Notification(token=token, payload=cls.payload) for token in cls.tokens]
        cls.topic = 'com.example.App'

    def setUp(self):
        self.open_streams = 0
        self.max_open_streams = 0
        self.mock_results = None
        self.next_stream_id = 0

        with patch('apns2.credentials.HTTP20Connection') as mock_connection_constructor, patch('apns2.credentials.init_context'):
            self.mock_connection = MagicMock()
            self.mock_connection.get_response.side_effect = self.mock_get_response
            self.mock_connection.request.side_effect = self.mock_request
            self.mock_connection._conn.__enter__.return_value = self.mock_connection._conn
            self.mock_connection._conn.remote_settings.max_concurrent_streams = 500
            mock_connection_constructor.return_value = self.mock_connection
            self.client = APNsClient(credentials=None)

    @contextlib.contextmanager
    def mock_get_response(self, stream_id):
        self.open_streams -= 1
        if self.mock_results:
            reason = self.mock_results[stream_id]
            response = Mock(status=200 if reason == 'Success' else 400)
            response.read.return_value = ('{"reason": "%s"}' % reason).encode('utf-8')
            yield response
        else:
            yield Mock(status=200)

    def mock_request(self, *dummy_args):
        self.open_streams += 1
        if self.open_streams > self.max_open_streams:
            self.max_open_streams = self.open_streams

        stream_id = self.next_stream_id
        self.next_stream_id += 1
        return stream_id

    def test_send_notification_batch_returns_results_in_order(self):
        results = self.client.send_notification_batch(self.notifications, self.topic)
        expected_results = {token: 'Success' for token in self.tokens}
        self.assertEqual(results, expected_results)

    def test_send_notification_batch_respects_max_concurrent_streams_from_server(self):
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams, 500)

    def test_send_notification_batch_overrides_server_max_concurrent_streams_if_too_large(self):
        self.mock_connection._conn.remote_settings.max_concurrent_streams = 5000
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams, CONCURRENT_STREAMS_SAFETY_MAXIMUM)

    def test_send_notification_batch_overrides_server_max_concurrent_streams_if_too_small(self):
        self.mock_connection._conn.remote_settings.max_concurrent_streams = 0
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams, 1)

    def test_send_notification_batch_reports_different_results(self):
        self.mock_results = (
            ['BadDeviceToken'] * 1000 + ['Success'] * 1000 + ['DeviceTokenNotForTopic'] * 2000 +
            ['Success'] * 1000 + ['BadDeviceToken'] * 500 + ['PayloadTooLarge'] * 4500
        )
        results = self.client.send_notification_batch(self.notifications, self.topic)
        expected_results = dict(zip(self.tokens, self.mock_results))
        self.assertEqual(results, expected_results)

    def test_send_empty_batch_does_nothing(self):
        self.client.send_notification_batch([], self.topic)
        self.assertEqual(self.mock_connection.request.call_count, 0)

    def test_connect_establishes_connection(self):
        self.client.connect()
        self.mock_connection.connect.assert_called_once_with()

    def test_connect_retries_failed_connection(self):
        self.mock_connection.connect.side_effect = [RuntimeError, RuntimeError, None]
        self.client.connect()
        self.assertEqual(self.mock_connection.connect.call_count, 3)

    def test_connect_stops_on_reaching_max_retries(self):
        self.mock_connection.connect.side_effect = [RuntimeError] * 4
        with self.assertRaises(ConnectionFailed):
            self.client.connect()

        self.assertEqual(self.mock_connection.connect.call_count, 3)
Esempio n. 4
0
class ClientTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        # Ignore all log messages so that test output is not cluttered.
        logging.basicConfig(level=logging.CRITICAL)
        cls.tokens = ['%064x' % i for i in range(10000)]
        cls.payload = Payload(alert='Test alert')
        cls.notifications = [
            Notification(token=token, payload=cls.payload)
            for token in cls.tokens
        ]
        cls.topic = 'com.example.App'

    def setUp(self):
        self.open_streams = 0
        self.max_open_streams = 0
        self.mock_results = None
        self.next_stream_id = 0

        with patch('apns2.credentials.HTTP20Connection'
                   ) as mock_connection_constructor, patch(
                       'apns2.credentials.init_context'):
            self.mock_connection = MagicMock()
            self.mock_connection.get_response.side_effect = self.mock_get_response
            self.mock_connection.request.side_effect = self.mock_request
            self.mock_connection._conn.__enter__.return_value = self.mock_connection._conn
            self.mock_connection._conn.remote_settings.max_concurrent_streams = 500
            mock_connection_constructor.return_value = self.mock_connection
            self.client = APNsClient(credentials=None)

    @contextlib.contextmanager
    def mock_get_response(self, stream_id):
        self.open_streams -= 1
        if self.mock_results:
            reason = self.mock_results[stream_id]
            response = Mock(status=200 if reason == 'Success' else 400)
            response.read.return_value = ('{"reason": "%s"}' %
                                          reason).encode('utf-8')
            yield response
        else:
            yield Mock(status=200)

    def mock_request(self, *dummy_args):
        self.open_streams += 1
        if self.open_streams > self.max_open_streams:
            self.max_open_streams = self.open_streams

        stream_id = self.next_stream_id
        self.next_stream_id += 1
        return stream_id

    def test_send_notification_batch_returns_results_in_order(self):
        results = self.client.send_notification_batch(self.notifications,
                                                      self.topic)
        expected_results = {token: 'Success' for token in self.tokens}
        self.assertEqual(results, expected_results)

    def test_send_notification_batch_respects_max_concurrent_streams_from_server(
            self):
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams, 500)

    def test_send_notification_batch_overrides_server_max_concurrent_streams_if_too_large(
            self):
        self.mock_connection._conn.remote_settings.max_concurrent_streams = 5000
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams,
                         CONCURRENT_STREAMS_SAFETY_MAXIMUM)

    def test_send_notification_batch_overrides_server_max_concurrent_streams_if_too_small(
            self):
        self.mock_connection._conn.remote_settings.max_concurrent_streams = 0
        self.client.send_notification_batch(self.notifications, self.topic)
        self.assertEqual(self.max_open_streams, 1)

    def test_send_notification_batch_reports_different_results(self):
        self.mock_results = (['BadDeviceToken'] * 1000 + ['Success'] * 1000 +
                             ['DeviceTokenNotForTopic'] * 2000 +
                             ['Success'] * 1000 + ['BadDeviceToken'] * 500 +
                             ['PayloadTooLarge'] * 4500)
        results = self.client.send_notification_batch(self.notifications,
                                                      self.topic)
        expected_results = dict(zip(self.tokens, self.mock_results))
        self.assertEqual(results, expected_results)

    def test_send_empty_batch_does_nothing(self):
        self.client.send_notification_batch([], self.topic)
        self.assertEqual(self.mock_connection.request.call_count, 0)

    def test_connect_establishes_connection(self):
        self.client.connect()
        self.mock_connection.connect.assert_called_once_with()

    def test_connect_retries_failed_connection(self):
        self.mock_connection.connect.side_effect = [
            RuntimeError, RuntimeError, None
        ]
        self.client.connect()
        self.assertEqual(self.mock_connection.connect.call_count, 3)

    def test_connect_stops_on_reaching_max_retries(self):
        self.mock_connection.connect.side_effect = [RuntimeError] * 4
        with self.assertRaises(ConnectionFailed):
            self.client.connect()

        self.assertEqual(self.mock_connection.connect.call_count, 3)