Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 300 |
FirebaseCloudMessagingService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 13 |
2970.00 | |
0.00% |
0 / 300 |
__construct | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
__clone | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
getInstance | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 5 |
|||
saveFcmToken | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 15 |
|||
addNewFcmToken | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 31 |
|||
updateFcmToken | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 24 |
|||
checkIfTokenExist | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 17 |
|||
deleteFcmToken | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 15 |
|||
getUserFcmTokens | |
0.00% |
0 / 1 |
42.00 | |
0.00% |
0 / 28 |
|||
sendPushNotification | |
0.00% |
0 / 1 |
156.00 | |
0.00% |
0 / 84 |
|||
getUserFcmTokensUsingEmailIds | |
0.00% |
0 / 1 |
56.00 | |
0.00% |
0 / 50 |
|||
deleteInvalidFcmTokens | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 8 |
|||
generateAccessToken | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 19 |
<?php | |
namespace com\linways\core\ams\professional\service\mobile; | |
use com\linways\base\constant\UserType; | |
use com\linways\core\ams\professional\constant\DevelopmentPlatform; | |
use com\linways\core\ams\professional\exception\mobile\MobileException; | |
use com\linways\core\ams\professional\request\GenerateFcmAccessTokenRequest; | |
use com\linways\core\ams\professional\request\mobile\GetUserFcmTokensUsingEmailIdsRequest; | |
use com\linways\core\ams\professional\request\mobile\PushNotificationRequest; | |
use com\linways\core\ams\professional\request\mobile\SaveFcmTokenRequest; | |
use com\linways\core\ams\professional\response\GenerateFcmAccessTokenResponse; | |
use com\linways\core\ams\professional\response\GetUserFcmTokensUsingEmailIdsResponse; | |
use com\linways\core\ams\professional\service\BaseService; | |
use Google\Auth\CredentialsLoader; | |
use Google\Auth\HttpHandler\HttpHandlerFactory; | |
use GuzzleHttp\Client; | |
use GuzzleHttp\HandlerStack; | |
use stdClass; | |
/** | |
* User: sarathks | |
* Date: 20/05/22 | |
*/ | |
class FirebaseCloudMessagingService extends BaseService | |
{ | |
private $mapper = []; | |
private $commonApiUtil = null; | |
private $logger = null; | |
private $cache; | |
/** | |
* Presence of a static member variable | |
* | |
* @var null | |
*/ | |
private static $_instance = null; | |
/** | |
* Locked down the constructor or Prevent any outside instantiation of this class | |
* | |
* FirebaseCloudMessagingService constructor. | |
*/ | |
private function __construct() | |
{ | |
} | |
/** | |
* Prevent any object or instance of that class to be cloned | |
*/ | |
private function __clone() | |
{ | |
} | |
/** | |
* Have a single globally accessible static method | |
* | |
* @return FirebaseCloudMessagingService|null | |
*/ | |
public static function getInstance() | |
{ | |
if (!is_object(self::$_instance)) | |
self::$_instance = new self(); | |
return self::$_instance; | |
} | |
/** | |
* Save FCM Token | |
* @param SaveFcmTokenRequest $request | |
* @return String $newFcmToken | |
* @throws MobileException | |
*/ | |
public function saveFcmToken(SaveFcmTokenRequest $request) | |
{ | |
try { | |
$isExist = $this->checkIfTokenExist($request->newFcmToken,$request->userId); | |
if($isExist) { | |
return; | |
} | |
if (empty($request->currentFcmToken)) { | |
return $this->addNewFcmToken($request); | |
} else { | |
return $this->updateFcmToken($request); | |
} | |
} catch (\Exception $e) { | |
throw $e; | |
} | |
} | |
/** | |
* Add new FCM Token | |
* @param SaveFcmTokenRequest $request | |
* @return String $newFcmToken | |
* @throws MobileException | |
*/ | |
public function addNewFcmToken(SaveFcmTokenRequest $request) | |
{ | |
if (empty($request->newFcmToken)) { | |
throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid fcm token"); | |
} | |
if (empty($request->userId)) { | |
throw new MobileException(MobileException::INVALID_USER_ID, "Invalid user id"); | |
} | |
if (empty($request->userType)) { | |
throw new MobileException(MobileException::INVALID_USER_TYPE, "Invalid user type"); | |
} | |
$sql="DELETE FROM v4_firebase_cloud_messaging_tokens where fcm_token = '$request->newFcmToken'"; | |
try { | |
$this->executeQuery($sql); | |
$sql = "INSERT | |
INTO | |
`v4_firebase_cloud_messaging_tokens` (`fcm_token`, | |
`user_id`, | |
`user_type`, | |
`platform`, | |
`is_valid`) | |
VALUES ('$request->newFcmToken', | |
'$request->userId', | |
'$request->userType', | |
'$request->platform', | |
'$request->isValid')"; | |
$this->executeQuery($sql); | |
return $request->newFcmToken; | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
/** | |
* Update FCM Token | |
* @param SaveFcmTokenRequest $request | |
* @return String $newFcmToken | |
* @throws MobileException | |
*/ | |
public function updateFcmToken(SaveFcmTokenRequest $request) | |
{ | |
if (empty($request->currentFcmToken)) { | |
throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid current fcm token"); | |
} | |
if (empty($request->newFcmToken)) { | |
throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid new fcm token"); | |
} | |
$sql = "UPDATE | |
`v4_firebase_cloud_messaging_tokens` | |
SET | |
fcm_token = '$request->newFcmToken', | |
created_date = UTC_TIMESTAMP() | |
WHERE | |
fcm_token = '$request->currentFcmToken' | |
AND user_id = '$request->userId' | |
AND platform = '$request->platform' | |
AND user_type = '$request->userType'"; | |
try { | |
$this->executeQuery($sql); | |
return $request->newFcmToken; | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
/** | |
* Check if same FCM Token exist for a user | |
* @param $fcmToken | |
* @return String $newFcmToken | |
* @throws MobileException | |
*/ | |
public function checkIfTokenExist($token, $userId) | |
{ | |
if (empty($token)) { | |
throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid fcm token"); | |
} | |
$sql = "SELECT | |
fcm_token | |
FROM | |
`v4_firebase_cloud_messaging_tokens` | |
WHERE | |
fcm_token = '$token' and user_id = '$userId'"; | |
try { | |
$tokens = $this->executeQueryForList($sql); | |
return count($tokens); | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
/** | |
* Delete FCM Token | |
* @param $request | |
* @throws MobileException | |
*/ | |
public function deleteFcmToken($request) | |
{ | |
// $request->fcmTokens is an array of tokens to be deleted; | |
if (empty($request->fcmTokens)) { | |
return; | |
} | |
$sql = "DELETE | |
FROM | |
`v4_firebase_cloud_messaging_tokens` | |
WHERE | |
fcm_token IN ('" . implode("','", $request->fcmTokens) . "')"; | |
try { | |
$this->executeQuery($sql); | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
public function getUserFcmTokens($fcmTokenRequestObject, $platform = DevelopmentPlatform::MOBILE) { | |
// fcmTokenRequestObject should contain the keys as usertype and values as array of ids of that type for eg:- | |
// $userIdObject = new stdClass(); | |
// $userIdObject->STAFF = ['200']; | |
// $userIdObject->STUDENT = ['3588']; | |
$whereCondArr = []; | |
$whereCond = ""; | |
foreach($fcmTokenRequestObject as $userType => $ids) { | |
$whereCondArr []= " (user_id IN ('" . implode("','", $ids) . "') AND user_type = '$userType') "; | |
} | |
if(!empty($whereCondArr)) { | |
$whereCond .= " AND (".implode(" OR ", $whereCondArr).")"; | |
} | |
if($platform){ | |
$whereCond .= " AND platform = '$platform' "; | |
} | |
if (empty($whereCondArr)) { | |
throw new MobileException(MobileException::INVALID_USER_ID, "Invalid user ids"); | |
} | |
$sql = "SELECT | |
fcm_token | |
FROM | |
`v4_firebase_cloud_messaging_tokens` WHERE 1=1 | |
$whereCond"; | |
try { | |
$tokens = (array)$this->executeQueryForList($sql); | |
$result = array_values(array_unique(array_map(function($token) { | |
return $token->fcm_token; | |
}, $tokens))); | |
return $result; | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
public function sendPushNotification(PushNotificationRequest $request) { | |
$url = 'https://fcm.googleapis.com/v1/projects/linways-b8708/messages:send'; | |
$accessTokenReq = new GenerateFcmAccessTokenRequest(); | |
$accessTokenData = $this->generateAccessToken($accessTokenReq); | |
$accessToken = $accessTokenData->accessToken; | |
// PREPARING THE FCM API REQUEST PARAM | |
$notificationObject = array( | |
'title' => $request->title ?? '', | |
'body' => $request->body ?? '' | |
); | |
$messageObject = array( | |
'notification' => $notificationObject | |
); | |
// PUSING DATA OBJECT IF PRESENT | |
$dataObject = $request->data; | |
if (!empty($dataObject)) { | |
$messageObject['data'] = $dataObject; | |
} | |
// PUSHING WEBPUSH OBJECT IF PRESENT | |
$webPushObject = array(); | |
if (!empty($request->webFcmOptionLink)) { | |
$webPushObject['fcm_options'] = array('link' => $request->webFcmOptionLink); | |
} | |
if (!empty($webPushObject)) { | |
$messageObject['webpush'] = $webPushObject; | |
} | |
$fields = array( | |
'message' => $messageObject | |
); | |
$headers = array( | |
'Content-Type:application/json', | |
'Authorization: Bearer ' . $accessToken | |
); | |
$ch = curl_init(); | |
curl_setopt($ch, CURLOPT_URL, $url); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
// EARLIER OUR REQUEST CONTAINED registration_ids KEY | |
$registationIds = $request->fcmTokens; | |
// FCM HTTP V1 API ONLY SUPPORT INDIVIDUAL TOKENS | |
if(!empty($registationIds) && count($registationIds) > 1000) { | |
// FOR NOW WE ARE SENDING TOKENS ONE BY ONE. IN FUTURE THIS CAN BE CHANGED TOPIC BASED. SUBSCRIBE ALL THESE TOKENS TO TOPIC AND THEN SEND TO TOPIC | |
while(count($registationIds)) { | |
$splicedRegistarationIds = array_splice($registationIds,0,1000); | |
$unRegisteredTokens = []; | |
foreach($splicedRegistarationIds as $token) { | |
$fields['message']['token'] = $token; | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
$result = curl_exec($ch); | |
$result = json_decode($result); | |
// ACCUMULATING UNREGISTERED TOKENS | |
$errorDetails = $result->error->details; | |
$errorCode = $errorDetails[0]->errorCode; | |
if ($errorCode == 'UNREGISTERED') { | |
$unRegisteredTokens[] = $token; | |
} | |
} | |
$deleteRequest = new \stdClass(); | |
$deleteRequest->fcmTokens = $unRegisteredTokens; | |
$this->deleteFcmToken($deleteRequest); | |
$unRegisteredTokens = []; | |
sleep(2); // SLEEP FOR 2 SECONDS TO AVOID RATE LIMITING | |
} | |
return; | |
} | |
else if(!empty($registationIds)) { | |
$unRegisteredTokens = []; | |
foreach($registationIds as $token) { | |
$fields['message']['token'] = $token; | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
$result = curl_exec($ch); | |
$result = json_decode($result); | |
$errorDetails = $result->error->details; | |
$errorCode = $errorDetails[0]->errorCode; | |
if ($errorCode == 'UNREGISTERED') { | |
$unRegisteredTokens[] = $token; | |
} | |
} | |
$deleteRequest = new \stdClass(); | |
$deleteRequest->fcmTokens = $unRegisteredTokens; | |
$this->deleteFcmToken($deleteRequest); | |
$unRegisteredTokens = []; | |
return; | |
} | |
// OTHER CASES OF NOT USING REGISTRATION IDS LIKE TOPIC AND ALL | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
$result = curl_exec($ch); | |
$result = json_decode($result); | |
$this->deleteInvalidFcmTokens($request->fields, $result); | |
} | |
/** | |
* Get user fcm tokens using user email ids | |
* | |
* request->userTypeEmailMap should be a map as follows | |
* $userIdObject = new stdClass(); | |
* $userIdObject->STAFF = ['staff1@email.com','staff2@email.com']; | |
* $userIdObject->STUDENT = ['student1@email.com','student2@email.com']; | |
* | |
* @param GetUserFcmTokensUsingEmailIdsRequest $request | |
* @return GetUserFcmTokensUsingEmailIdsResponse $responseData | |
* @throws MobileException | |
**/ | |
public function getUserFcmTokensUsingEmailIds(GetUserFcmTokensUsingEmailIdsRequest $request) | |
{ | |
try { | |
$fcmTokens = []; | |
$userTypeEmailIdMap = $request->userTypeEmailMap; | |
if (!empty($request->platform)) { | |
$platformCond = " AND vfcmt.platform = '$request->platform' "; | |
} | |
foreach($userTypeEmailIdMap as $userType => $emailIds) { | |
if (!empty($emailIds)) { | |
if ($userType === UserType::STAFF) { | |
$sql = "SELECT | |
vfcmt.fcm_token | |
FROM | |
staffaccounts s | |
INNER JOIN v4_firebase_cloud_messaging_tokens vfcmt ON | |
vfcmt.user_id = s.staffID | |
AND vfcmt.user_type = 'STAFF' | |
WHERE | |
s.staffEmail IN ('" . implode("','", $emailIds) . "') | |
$platformCond "; | |
$tokenList = $this->executeQueryForList($sql); | |
$staffTokens = array_values(array_unique(array_map(function($token) { | |
return $token->fcm_token; | |
}, $tokenList))); | |
$fcmTokens = array_merge($fcmTokens, $staffTokens); | |
} | |
else if ($userType === UserType::STUDENT) { | |
$sql = "SELECT | |
vfcmt.fcm_token | |
FROM | |
studentaccount s | |
INNER JOIN v4_firebase_cloud_messaging_tokens vfcmt ON | |
vfcmt.user_id = s.studentID | |
AND vfcmt.user_type = 'STUDENT' | |
WHERE | |
s.studentEmail IN ('" . implode("','", $emailIds) . "') | |
$platformCond "; | |
$tokenList = $this->executeQueryForList($sql); | |
$studentTokens = array_values(array_unique(array_map(function($token) { | |
return $token->fcm_token; | |
}, $tokenList))); | |
$fcmTokens = array_merge($fcmTokens, $studentTokens); | |
} | |
} | |
} | |
$responseData = new GetUserFcmTokensUsingEmailIdsResponse(); | |
$responseData->fcmTokens = $fcmTokens; | |
return $responseData; | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
/** | |
* Delete FCM Tokens that are invalid | |
* @param $fcmResult | |
* @throws MobileException | |
*/ | |
protected function deleteInvalidFcmTokens($fcmRequestFields, $fcmResult) { | |
if(!empty($fcmRequestFields['registration_ids']) && $fcmResult->failure > 0) { | |
$failedTokens = array_filter($fcmRequestFields['registration_ids'], function($token, $key) use ($fcmResult) { | |
return $fcmResult->results[$key]->error == 'NotRegistered' || $fcmResult->results[$key]->error == 'InvalidRegistration'; | |
}, ARRAY_FILTER_USE_BOTH); | |
$deleteRequest = new \stdClass(); | |
$deleteRequest->fcmTokens = $failedTokens; | |
$this->deleteFcmToken($deleteRequest); | |
} | |
} | |
/** | |
* Generate Access token for calling FCM api | |
* | |
* This method generates access token for calling fcm http v1 api | |
* | |
* @param GenerateFcmAccessTokenRequest $request | |
* @return GenerateFcmAccessTokenResponse $response | |
* @throws MobileException | |
**/ | |
public function generateAccessToken(GenerateFcmAccessTokenRequest $request) | |
{ | |
try { | |
$credentialsPath = $GLOBALS['GOOGLE_FIREBASE_CREDENTIALS_PATH']; | |
$credentialJsonContent = file_get_contents($credentialsPath); | |
$credentialJson = json_decode($credentialJsonContent, true); | |
$credentials = CredentialsLoader::makeCredentials( | |
['https://www.googleapis.com/auth/firebase.messaging'], | |
$credentialJson | |
); | |
$httpHandler = HttpHandlerFactory::build(new Client(['handler' => HandlerStack::create()])); | |
$credentials->fetchAuthToken($httpHandler); | |
$accessToken = $credentials->getLastReceivedToken()['access_token']; | |
$response = new GenerateFcmAccessTokenResponse(); | |
$response->accessToken = $accessToken; | |
return $response; | |
} catch (\Exception $e) { | |
throw new MobileException($e->getCode(), $e->getMessage()); | |
} | |
} | |
} |