Create new FCM sender service to replace outdated library #799
This commit is contained in:
parent
b8666ff5e0
commit
33f48647b6
3 changed files with 260 additions and 36 deletions
|
|
@ -33,6 +33,7 @@
|
|||
"doctrine/doctrine-migrations-bundle": "^2",
|
||||
"doctrine/orm": "^2",
|
||||
"edwinhoksberg/php-fcm": "dev-notif-priority-hotfix",
|
||||
"firebase/php-jwt": "^6.10",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"hashids/hashids": "^4.1",
|
||||
"jankstudio/catalyst-api-bundle": "dev-master",
|
||||
|
|
|
|||
65
composer.lock
generated
65
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "653f8558c75614dd65421cb3eb48c29b",
|
||||
"content-hash": "4676209ee947dbcf3cfcf937c838c6f2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/package-versions-deprecated",
|
||||
|
|
@ -1827,6 +1827,69 @@
|
|||
},
|
||||
"time": "2023-07-19T09:04:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v6.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "500501c2ce893c824c801da135d02661199f60c5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5",
|
||||
"reference": "500501c2ce893c824c801da135d02661199f60c5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Firebase\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Neuman Vong",
|
||||
"email": "neuman+pear@twilio.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Anant Narayanan",
|
||||
"email": "anant@php.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"keywords": [
|
||||
"jwt",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.10.1"
|
||||
},
|
||||
"time": "2024-05-18T18:05:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/proxy-manager-lts",
|
||||
"version": "v1.0.5",
|
||||
|
|
|
|||
|
|
@ -5,55 +5,178 @@ namespace App\Service;
|
|||
use App\Entity\Customer;
|
||||
use App\Ramcar\FirebaseNotificationType;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Fcm\FcmClient;
|
||||
use Fcm\Push\Notification;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Firebase\JWT\JWT;
|
||||
use App\Entity\JobOrder;
|
||||
use App\Entity\Subscription;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class FCMSender
|
||||
{
|
||||
protected $client;
|
||||
protected $translator;
|
||||
protected $project_id;
|
||||
protected $base_uri;
|
||||
protected $credentials;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, $server_key, $sender_id)
|
||||
public function __construct(TranslatorInterface $translator, string $creds_file, string $project_id, string $base_uri)
|
||||
{
|
||||
$this->client = new FcmClient($server_key, $sender_id);
|
||||
$this->translator = $translator;
|
||||
$this->project_id = $project_id;
|
||||
$this->base_uri = $base_uri;
|
||||
|
||||
// check credentials file
|
||||
if (!file_exists($creds_file)) {
|
||||
throw new RuntimeException("Service account JSON file not found: $creds_file");
|
||||
}
|
||||
|
||||
// set credentials from file
|
||||
$this->credentials = json_decode(file_get_contents($creds_file), true);
|
||||
|
||||
// instantiate client
|
||||
$this->client = new Client([
|
||||
'base_uri' => $base_uri . 'v1/',
|
||||
'timeout' => 5.0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function send($recipients, $title, $body, $data = [], $color = null, $sound = null, $badge = null)
|
||||
private function generateAccessToken()
|
||||
{
|
||||
$notification = new Notification();
|
||||
$notification->setTitle($title)
|
||||
->setBody($body);
|
||||
// build the access token parts
|
||||
$private_key = $this->credentials['private_key'];
|
||||
$now = time();
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$notification->addRecipient($recipient);
|
||||
}
|
||||
$jwt_payload = [
|
||||
'iss' => $this->credentials['client_email'],
|
||||
'sub' => $this->credentials['client_email'],
|
||||
'aud' => $this->base_uri,
|
||||
'iat' => $now,
|
||||
'exp' => $now + 3600,
|
||||
];
|
||||
|
||||
if (!empty($color)) {
|
||||
$notification->setColor($color);
|
||||
}
|
||||
|
||||
if (!empty($sound)) {
|
||||
$notification->setSound($sound);
|
||||
}
|
||||
|
||||
if (!empty($color)) {
|
||||
$notification->setColor($color);
|
||||
}
|
||||
|
||||
if (!empty($badge)) {
|
||||
$notification->setBadge($badge);
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
$notification->addDataArray($data);
|
||||
}
|
||||
|
||||
return $this->client->send($notification);
|
||||
// encode into JWT
|
||||
return JWT::encode($jwt_payload, $private_key, 'RS256');
|
||||
}
|
||||
|
||||
public function sendJoEvent(JobOrder $job_order, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
public function send($recipients, $title, $body, $data = [], $color = null, $sound = null, $image = null)
|
||||
{
|
||||
// set URL for sending
|
||||
$url = "projects/{$this->project_id}/messages:send";
|
||||
$access_token = $this->generateAccessToken();
|
||||
|
||||
// build payload structure
|
||||
$payload = [
|
||||
'message' => [
|
||||
'notification' => [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
],
|
||||
'android' => [
|
||||
'priority' => 'high',
|
||||
'notification' => [
|
||||
'sound' => $sound ?? 'default',
|
||||
],
|
||||
],
|
||||
'apns' => [
|
||||
'headers' => [
|
||||
'apns-priority' => '10',
|
||||
],
|
||||
'payload' => [
|
||||
'aps' => [
|
||||
'alert' => [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
],
|
||||
'sound' => $sound ?? 'default',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// if image is provided, apply params
|
||||
if (!empty($image)) {
|
||||
$payload['message']['notification']['image'] = $image;
|
||||
$payload['message']['android']['notification']['image'] = $image;
|
||||
$payload['message']['apns']['payload']['aps']['mutable-content'] = 1;
|
||||
}
|
||||
|
||||
// if data is provided, attach it
|
||||
if (!empty($data)) {
|
||||
$payload['message']['data'] = $data;
|
||||
}
|
||||
|
||||
// build headers for request
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
|
||||
// send the message to each recipient
|
||||
foreach ($recipients as $recipient) {
|
||||
$payload['message']['token'] = $recipient;
|
||||
|
||||
try {
|
||||
$response = $this->client->post($url, [
|
||||
'headers' => $headers,
|
||||
'json' => $payload,
|
||||
]);
|
||||
|
||||
$result = $response->getBody();
|
||||
$json = json_decode($result, true);
|
||||
|
||||
// log response
|
||||
$this->log(
|
||||
$title,
|
||||
$body,
|
||||
'Success',
|
||||
'success',
|
||||
json_encode($payload),
|
||||
$result,
|
||||
);
|
||||
|
||||
// message sent!
|
||||
return [
|
||||
'success' => true,
|
||||
'response' => $json,
|
||||
];
|
||||
} catch (RequestException $e) {
|
||||
$error_msg = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : $e->getMessage();
|
||||
|
||||
// something went wrong
|
||||
$this->log(
|
||||
$title,
|
||||
$body,
|
||||
$error_msg,
|
||||
'error',
|
||||
json_encode($payload),
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => "Request error: " . $error_msg,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
// something went wrong
|
||||
$this->log(
|
||||
$title,
|
||||
$body,
|
||||
$e->getMessage(),
|
||||
'error',
|
||||
json_encode($payload),
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => "Unexpected error: " . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function sendJoEvent(JobOrder $job_order, $title, $body, $data = [])
|
||||
{
|
||||
// get customer object
|
||||
$cust = $job_order->getCustomer();
|
||||
|
|
@ -64,7 +187,7 @@ class FCMSender
|
|||
$data['notification_type'] = FirebaseNotificationType::JOB_ORDER;
|
||||
|
||||
// send the event
|
||||
return $this->sendEvent($cust, $title, $body, $data, $title_params, $body_params);
|
||||
return $this->sendEvent($cust, $title, $body, $data);
|
||||
}
|
||||
|
||||
public function sendLoyaltyEvent(Customer $cust, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
|
|
@ -76,6 +199,18 @@ class FCMSender
|
|||
return $this->sendEvent($cust, $title, $body, $data, $title_params, $body_params);
|
||||
}
|
||||
|
||||
public function sendSubscriptionEvent(Subscription $sub, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
{
|
||||
// get customer object
|
||||
$cust = $sub->getCustomer();
|
||||
|
||||
// attach type info
|
||||
$data['notification_type'] = FirebaseNotificationType::SUBSCRIPTION;
|
||||
|
||||
// send the event
|
||||
return $this->sendEvent($cust, $title, $body, $data, $title_params, $body_params);
|
||||
}
|
||||
|
||||
public function sendEvent(Customer $cust, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
{
|
||||
// get all v2 devices
|
||||
|
|
@ -86,7 +221,12 @@ class FCMSender
|
|||
}
|
||||
|
||||
// send fcm notification
|
||||
$result = $this->send(array_keys($devices), $this->translator->trans($title), $this->translator->trans($body), $data);
|
||||
$result = $this->send(
|
||||
array_keys($devices),
|
||||
$this->translator->trans($title, $title_params),
|
||||
$this->translator->trans($body, $body_params),
|
||||
$data,
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
@ -123,4 +263,24 @@ class FCMSender
|
|||
|
||||
return $device_ids;
|
||||
}
|
||||
|
||||
// TODO: make this more elegant
|
||||
public function log($title, $body, $message, $type, $data = "[]", $result = "[]")
|
||||
{
|
||||
$filename = '/../../var/log/fcm_' . $type . '.log';
|
||||
$date = date("Y-m-d H:i:s");
|
||||
|
||||
// build log entry
|
||||
$entry = implode("\r\n", array_filter([
|
||||
$date,
|
||||
$title,
|
||||
$body,
|
||||
$message,
|
||||
"DATA:\r\n" . $data,
|
||||
"RESULT:\r\n" . $result,
|
||||
"\r\n----------------------------------------\r\n\r\n",
|
||||
]));
|
||||
|
||||
file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue