Files
intaleq_v2/app/Services/FcmService.php
2026-04-22 23:16:23 +03:00

183 lines
5.9 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\Log;
/**
* خدمة إشعارات Firebase (FCM Service)
*
* الغرض من الملف:
* إرسال التنبيهات (Push Notifications) للهواتف الذكية عبر خدمة Google Firebase.
*
* كيفية العمل:
* 1. يتصل بخوادم Google باستخدام مفاتيح الوصول (API Keys).
* 2. يرسل التنبيه للراكب أو السائق بناءً على الرمز (Token) الخاص بجهازه.
* 3. يدير إرسال البيانات الإضافية (مثل معرف الرحلة) داخل التنبيه لتسهيل التفاعل معه داخل التطبيق.
*/
class FcmService
{
private ?string $accessToken = null;
private string $credentialsPath;
private string $cachePath;
public function __construct()
{
$this->credentialsPath = config('intaleq.fcm_credentials_path');
$this->cachePath = config('intaleq.fcm_cache_path');
}
/**
* Send FCM notification to a specific device token
*/
public function sendToDevice(string $token, string $title, string $body, array $data = [], string $category = ''): array
{
if (empty($token)) {
return ['status' => 'error', 'message' => 'Empty token'];
}
// Add category to data payload
if ($category) {
$data['category'] = $category;
}
// Convert non-string data values
$stringData = [];
foreach ($data as $key => $value) {
$stringData[$key] = is_array($value) ? json_encode($value) : (string) $value;
}
$payload = [
'message' => [
'token' => $token,
'notification' => [
'title' => $title,
'body' => $body,
],
'data' => $stringData,
'android' => [
'priority' => 'high',
],
'apns' => [
'payload' => [
'aps' => [
'sound' => 'default',
'badge' => 1,
],
],
],
],
];
return $this->sendRequest($payload);
}
/**
* Send to FCM topic
*/
public function sendToTopic(string $topic, string $title, string $body, array $data = []): array
{
$payload = [
'message' => [
'topic' => $topic,
'notification' => [
'title' => $title,
'body' => $body,
],
'data' => array_map('strval', $data),
],
];
return $this->sendRequest($payload);
}
private function sendRequest(array $payload): array
{
$accessToken = $this->getAccessToken();
if (!$accessToken) {
return ['status' => 'error', 'message' => 'Failed to get access token'];
}
$credentials = json_decode(file_get_contents($this->credentialsPath), true);
$projectId = $credentials['project_id'] ?? '';
$url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$accessToken}",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
return ['status' => 'success', 'response' => json_decode($result, true)];
}
Log::error("[FCM] Error {$httpCode}: {$result}");
return ['status' => 'error', 'code' => $httpCode, 'response' => $result];
}
private function getAccessToken(): ?string
{
// Check cache
if (file_exists($this->cachePath)) {
$cached = json_decode(file_get_contents($this->cachePath), true);
if ($cached && ($cached['expires_at'] ?? 0) > time() + 60) {
return $cached['token'];
}
}
if (!file_exists($this->credentialsPath)) return null;
$credentials = json_decode(file_get_contents($this->credentialsPath), true);
$clientEmail = $credentials['client_email'];
$privateKey = $credentials['private_key'];
$now = time();
$header = rtrim(strtr(base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])), '+/', '-_'), '=');
$claim = rtrim(strtr(base64_encode(json_encode([
'iss' => $clientEmail,
'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
'aud' => 'https://oauth2.googleapis.com/token',
'exp' => $now + 3600,
'iat' => $now,
])), '+/', '-_'), '=');
$signature = '';
openssl_sign("{$header}.{$claim}", $signature, $privateKey, 'SHA256');
$jwt = "{$header}.{$claim}." . rtrim(strtr(base64_encode($signature), '+/', '-_'), '=');
$ch = curl_init('https://oauth2.googleapis.com/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt,
]),
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$token = json_decode($res, true)['access_token'] ?? null;
if ($token) {
file_put_contents($this->cachePath, json_encode([
'token' => $token,
'expires_at' => time() + 3500,
]));
}
return $token;
}
}