Skip to content

Commit

Permalink
FCM: Return PushNotificationStatus for broadcast notification
Browse files Browse the repository at this point in the history
  • Loading branch information
brianstoop committed Dec 12, 2024
1 parent 125f9b3 commit 97eeb3b
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 6 deletions.
94 changes: 92 additions & 2 deletions src/Lunr/Vortex/FCM/FCMBatchResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace Lunr\Vortex\FCM;

use InvalidArgumentException;
use Lunr\Vortex\PushNotificationBroadcastResponseInterface;
use Lunr\Vortex\PushNotificationResponseInterface;
use Lunr\Vortex\PushNotificationStatus;
use Psr\Log\LoggerInterface;
Expand All @@ -21,7 +22,7 @@
/**
* Firebase Cloud Messaging Push Notification response wrapper.
*/
class FCMBatchResponse implements PushNotificationResponseInterface
class FCMBatchResponse implements PushNotificationResponseInterface, PushNotificationBroadcastResponseInterface
{
/**
* Shared instance of a Logger class.
Expand All @@ -47,6 +48,12 @@ class FCMBatchResponse implements PushNotificationResponseInterface
*/
private array $endpoints;

/**
* The status for a broadcast.
* @var PushNotificationStatus
*/
protected PushNotificationStatus $broadcast_status;

/**
* Set of error types that indicate a curl error.
* @var array
Expand All @@ -71,7 +78,11 @@ public function __construct(array $responses, LoggerInterface $logger, array $en
$this->endpoints = $endpoints;
$this->responses = $responses;

if (array_is_list($responses) && count($responses) === 1)
if ($endpoints === [])
{
$this->set_broadcast_status();
}
elseif (array_is_list($responses) && count($responses) === 1)
{
foreach ($endpoints as $endpoint)
{
Expand All @@ -93,6 +104,7 @@ public function __destruct()
unset($this->responses);
unset($this->statuses);
unset($this->endpoints);
unset($this->broadcast_status);
}

/**
Expand Down Expand Up @@ -215,6 +227,84 @@ private function report_endpoint_error(string $endpoint, Response $response): vo
$this->statuses[$endpoint] = $status;
}

/**
* Get notification delivery status for a broadcast.
*
* @return PushNotificationStatus Delivery status for the broadcast
*/
public function get_broadcast_status(): PushNotificationStatus
{
return $this->broadcast_status ?? PushNotificationStatus::Unknown;
}

/**
* Set endpoint statuses
*
* @return void
*/
private function set_broadcast_status(): void
{
$response = $this->responses[0];

if ($response instanceof RequestsException)
{
$this->logger->warning(
'Dispatching FCM broadcast failed: {error}',
[ 'error' => $response->getMessage() ]
);

if (in_array($response->getType(), self::CURL_ERROR_TYPES))
{
$this->broadcast_status = PushNotificationStatus::TemporaryError;
return;
}

$this->broadcast_status = PushNotificationStatus::Unknown;
return;
}

$json_content = json_decode($response->body, TRUE);

$error_message = $json_content['error']['message'] ?? NULL;
$error_code = $json_content['error']['details'][0]['errorCode'] ?? NULL;

switch ($response->status_code)
{
case 200:
$this->broadcast_status = PushNotificationStatus::Success;
return;
case 400:
$status = PushNotificationStatus::Error;
$error_message ??= 'Invalid argument';
break;
case 401:
$status = PushNotificationStatus::Error;
$error_message ??= 'Error with authentication';
break;
case 429:
$status = PushNotificationStatus::TemporaryError;
$error_message ??= 'Exceeded qouta error';
break;
case 500:
$status = PushNotificationStatus::TemporaryError;
$error_message ??= 'Internal error';
break;
case 503:
$status = PushNotificationStatus::TemporaryError;
$error_message ??= 'Timeout';
break;
default:
$status = PushNotificationStatus::Unknown;
$error_message ??= $error_code ?? 'Unknown error';
break;
}

$context = [ 'error' => $error_message ];
$this->logger->warning('Dispatching FCM broadcast failed: {error}', $context);

$this->broadcast_status = $status;
}

}

?>
4 changes: 3 additions & 1 deletion src/Lunr/Vortex/FCM/FCMDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ public function push(object $payload, array &$endpoints): FCMResponse

if ($payload->is_broadcast())
{
$fcm_response->add_batch_response($this->push_batch($payload, $endpoints), $endpoints);
$batch_response = $this->push_batch($payload, $endpoints);

$fcm_response->add_broadcast_response($batch_response);

return $fcm_response;
}
Expand Down
32 changes: 31 additions & 1 deletion src/Lunr/Vortex/FCM/FCMResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

namespace Lunr\Vortex\FCM;

use Lunr\Vortex\PushNotificationBroadcastResponseInterface;
use Lunr\Vortex\PushNotificationResponseInterface;
use Lunr\Vortex\PushNotificationStatus;

/**
* Firebase Cloud Messaging Push Notification response wrapper.
*/
class FCMResponse implements PushNotificationResponseInterface
class FCMResponse implements PushNotificationResponseInterface, PushNotificationBroadcastResponseInterface
{

/**
Expand All @@ -25,6 +26,12 @@ class FCMResponse implements PushNotificationResponseInterface
*/
protected array $statuses;

/**
* The status for a broadcast.
* @var PushNotificationStatus
*/
protected PushNotificationStatus $broadcast_status;

/**
* Constructor.
*/
Expand All @@ -39,6 +46,7 @@ public function __construct()
public function __destruct()
{
unset($this->statuses);
unset($this->broadcast_status);
}

/**
Expand All @@ -57,6 +65,18 @@ public function add_batch_response(FCMBatchResponse $batch_response, array $endp
}
}

/**
* Add the results of a batch response.
*
* @param FCMBatchResponse $batch_response Push response.
*
* @return void
*/
public function add_broadcast_response(FCMBatchResponse $batch_response): void
{
$this->broadcast_status = $batch_response->get_broadcast_status();
}

/**
* Get notification delivery status for an endpoint.
*
Expand All @@ -69,6 +89,16 @@ public function get_status(string $endpoint): PushNotificationStatus
return isset($this->statuses[$endpoint]) ? $this->statuses[$endpoint] : PushNotificationStatus::Unknown;
}

/**
* Get notification delivery status for a broadcast.
*
* @return PushNotificationStatus Delivery status for the broadcast
*/
public function get_broadcast_status(): PushNotificationStatus
{
return $this->broadcast_status ?? PushNotificationStatus::Unknown;
}

}

?>
153 changes: 153 additions & 0 deletions src/Lunr/Vortex/FCM/Tests/FCMBatchResponseBasePushTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,159 @@ public function testPushErrorsMultiple(): void
$this->assertPropertySame('responses', $responses);
}

/**
* Test constructor behavior for push success with broadcast success.
*
* @covers Lunr\Vortex\FCM\FCMBatchResponse::__construct
*/
public function testPushSuccessWithBroadcastSuccess(): void
{
$content = file_get_contents(TEST_STATICS . '/Vortex/fcm/response_single_success.json');
$endpoints = [];

$this->response->status_code = 200;
$this->response->body = $content;

$this->logger->expects($this->never())
->method('warning');

$responses = [
$this->response,
];

$this->class = new FCMBatchResponse($responses, $this->logger, $endpoints);

parent::baseSetUp($this->class);

$broadcast_status = PushNotificationStatus::Success;

$this->assertPropertySame('logger', $this->logger);
$this->assertPropertySame('endpoints', $endpoints);
$this->assertPropertySame('broadcast_status', $broadcast_status);
$this->assertPropertySame('responses', $responses);
}

/**
* Test constructor behavior for error of push notification in case of failed request.
*
* @covers Lunr\Vortex\FCM\FCMBatchResponse::__construct
*/
public function testPushErrorFailedRequestWithBroadcast(): void
{
$this->mock_function('curl_errno', function () { return 10; });

$endpoints = [];

$responses = [ new RequestsException('cURL error 10: Request error', 'curlerror', NULL) ];

$this->logger->expects($this->once())
->method('warning')
->with(
'Dispatching FCM broadcast failed: {error}',
[ 'error' => 'cURL error 10: Request error' ]
);

$this->class = new FCMBatchResponse($responses, $this->logger, $endpoints);

parent::baseSetUp($this->class);

$this->assertPropertySame('logger', $this->logger);
$this->assertPropertySame('endpoints', $endpoints);
$this->assertPropertySame('broadcast_status', PushNotificationStatus::TemporaryError);
$this->assertPropertySame('responses', $responses);

$this->unmock_function('curl_errno');
}

/**
* Test constructor behavior for error of push notification in case of failed request.
*
* @covers Lunr\Vortex\FCM\FCMBatchResponse::__construct
*/
public function testPushErrorUnknownFailedRequestWithBroadcast(): void
{
$this->mock_function('curl_errno', function () { return 10; });

$endpoints = [];

$responses = [ new RequestsException('Unknown error', 'error', NULL) ];

$this->logger->expects($this->once())
->method('warning')
->with(
'Dispatching FCM broadcast failed: {error}',
[ 'error' => 'Unknown error' ]
);

$this->class = new FCMBatchResponse($responses, $this->logger, $endpoints);

parent::baseSetUp($this->class);

$this->assertPropertySame('logger', $this->logger);
$this->assertPropertySame('endpoints', $endpoints);
$this->assertPropertySame('broadcast_status', PushNotificationStatus::Unknown);
$this->assertPropertySame('responses', $responses);

$this->unmock_function('curl_errno');
}

/**
* Unit test data provider.
*
* @return array $data array of fcm errors
*/
public function errorBroadcastDataProvider(): array
{
$data = [];

$data[] = [ 'Invalid argument', 400, PushNotificationStatus::Error ];
$data[] = [ 'Error with authentication', 401, PushNotificationStatus::Error ];
$data[] = [ 'Exceeded qouta error', 429, PushNotificationStatus::TemporaryError ];
$data[] = [ 'Internal error', 500, PushNotificationStatus::TemporaryError ];
$data[] = [ 'Timeout', 503, PushNotificationStatus::TemporaryError ];
$data[] = [ 'Unknown error', 440, PushNotificationStatus::Unknown ];

return $data;
}

/**
* Test constructor behavior for push success with broadcast success.
*
* @param string $error_msg Error message.
* @param int $http_code Http code of the response.
* @param PushNotificationStatus $status The expected status.
*
* @dataProvider errorBroadcastDataProvider
* @covers Lunr\Vortex\FCM\FCMBatchResponse::__construct
*/
public function testPushWithBroadcastFailures(string $error_msg, int $http_code, PushNotificationStatus $status): void
{
$endpoints = [];

$this->response->status_code = $http_code;
$this->response->body = '';

$this->logger->expects($this->once())
->method('warning')
->with(
'Dispatching FCM broadcast failed: {error}',
[ 'error' => $error_msg ]
);

$responses = [
$this->response,
];

$this->class = new FCMBatchResponse($responses, $this->logger, $endpoints);

parent::baseSetUp($this->class);

$this->assertPropertySame('logger', $this->logger);
$this->assertPropertySame('endpoints', $endpoints);
$this->assertPropertySame('broadcast_status', $status);
$this->assertPropertySame('responses', $responses);
}

}

?>
Loading

0 comments on commit 97eeb3b

Please sign in to comment.