Initial implementation of FCM HTTP v1 API

This commit is contained in:
Kijin Sung 2024-04-21 20:18:39 +09:00
parent 6641e6a6ef
commit 9699ed3416
2 changed files with 143 additions and 4 deletions

View file

@ -288,7 +288,7 @@ class Push
*/
public function setAndroidChannelId(string $android_channel_id): bool
{
$this->metadata['android_channel_id'] = utf8_trim(utf8_clean($android_channel_id));
$this->metadata['channel_id'] = utf8_trim(utf8_clean($android_channel_id));
return true;
}
@ -374,10 +374,11 @@ class Push
$tokens = $this->_getDeviceTokens();
$output = null;
// Android FCM
// FCM HTTP v1 or Legacy API
if(count($tokens->fcm))
{
$fcm_driver = $this->getDriver('fcm');
$fcm_driver_name = array_key_exists('fcmv1', config('push.types') ?: []) ? 'fcmv1' : 'fcm';
$fcm_driver = $this->getDriver($fcm_driver_name);
$output = $fcm_driver->send($this, $tokens->fcm);
$this->sent += count($output->success);
$this->success_tokens = $output ? $output->success : [];

View file

@ -4,13 +4,27 @@ namespace Rhymix\Framework\Drivers\Push;
use Rhymix\Framework\HTTP;
use Rhymix\Framework\Push;
use Rhymix\Framework\Storage;
use Rhymix\Framework\Drivers\PushInterface;
use Rhymix\Framework\Helpers\CacheItemPoolHelper;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenCache;
use Google\Auth\Middleware\AuthTokenMiddleware;
use GuzzleHttp\HandlerStack;
/**
* The FCM HTTP v1 API Push driver.
*/
class FCMv1 extends Base implements PushInterface
{
/**
* Default settings.
*/
const API_URL = 'https://fcm.googleapis.com/v1/projects/%s/messages:send';
const BASE_URL = 'https://www.googleapis.com';
const SCOPES = ['https://www.googleapis.com/auth/firebase.messaging'];
const CHUNK_SIZE = 10;
/**
* Config keys used by this driver are stored here.
*/
@ -50,6 +64,130 @@ class FCMv1 extends Base implements PushInterface
*/
public function send(Push $message, array $tokens): \stdClass
{
return new \stdClass;
$output = new \stdClass;
$output->success = [];
$output->invalid = [];
$output->needUpdate = [];
// Configure Google OAuth2 access token, with appropriate caching.
$service_account = Storage::read($this->_config['service_account'] ?? '');
$service_account = json_decode($service_account);
if (!$service_account || empty($service_account['project_id']))
{
$message->addError('FCM error: service account JSON file cannot be decoded');
return $output;
}
$creds = CredentialsLoader::makeCredentials(self::SCOPES, $service_account);
$cache_helper = new CacheItemPoolHelper(true);
$cache_creds = new FetchAuthTokenCache($creds, [], $cache_helper);
// Configure Guzzle middleware.
$middleware = new AuthTokenMiddleware($cache_creds);
$stack = HandlerStack::create();
$stack->push($middleware);
// Compose common parts of the payload, leaving the token empty.
$api_url = sprintf(self::API_URL, $service_account['project_id']);
$payload = ['message' => []];
$title = $message->getSubject();
$body = $message->getContent();
if ($title !== '')
{
$payload['message']['notification']['title'] = $title;
}
if ($body !== '')
{
$payload['message']['notification']['body'] = $body;
}
$metadata = $message->getMetadata();
if (count($metadata))
{
$metadata['sound'] = isset($metadata['sound']) ? $metadata['sound'] : 'default';
$payload['message']['android'] = $metadata;
$payload['message']['webpush'] = $metadata;
$payload['message']['apns']['payload'] = [
'aps' => [
'alert' => ['title' => $title, 'body' => $body],
'sound' => $metadata['sound'],
'badge' => $metadata['badge'] ?? '',
'category' => $metadata['click_action'] ?? '',
],
];
}
$data = $message->getData();
if (count($data))
{
$payload['message']['data'] = $data;
}
// Send a notification to each token, grouped into chunks to speed up the process.
$chunked_tokens = array_chunk($tokens, self::CHUNK_SIZE);
foreach($chunked_tokens as $tokens)
{
$requests = [];
foreach ($tokens as $i => $token)
{
$requests[$i] = [
'url' => $api_url,
'method' => 'POST',
'data' => null,
'headers' => [],
'cookies' => [],
'settings' => [
'auth' => 'google_auth',
'base_uri' => self::BASE_URL,
'handler' => $stack,
'json' => $payload,
],
];
$requests[$i]['settings']['json']['message']['token'] = $token;
}
$responses = HTTP::multiple($requests);
// TODO: response parsing
foreach ($responses as $response)
{
if ($response->getStatusCode() === 200)
{
$decoded_response = json_decode($response->getBody());
var_dump($response->getStatusCode(), $decoded_response);
if (!$decoded_response)
{
$message->addError('FCM error: Invalid Response: '. $response);
return $output;
}
$results = $decoded_response->results ?: [];
foreach ($results as $result)
{
if ($result->error)
{
$message->addError('FCM error: '. $result->error);
$output->invalid[$token] = $token;
}
elseif ($result->message_id && $result->registration_id)
{
$output->needUpdate[$token] = $result->registration_id;
}
else
{
$output->success[$token] = $result->message_id;
}
}
}
else
{
$decoded_response = json_decode($response->getBody());
var_dump($response->getStatusCode(), $decoded_response);
$message->addError('FCM error: HTTP ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
}
}
}
return $output;
}
}