| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- <?php
- namespace App\Services\Fcm;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\Log;
- use Throwable;
- class FcmService
- {
- protected string $projectId;
- protected string $accessToken;
- public function __construct()
- {
- $this->projectId = config('fcm.project_id');
- $this->accessToken = $this->getAccessToken();
- }
- public function send(string $token, string $title, string $body, ?string $image = null): ?array
- {
- $url = "https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send";
- $message = [
- 'message' => [
- 'token' => $token,
- 'notification' => [
- 'title' => $title,
- 'body' => $body,
- 'image' => $image,
- ],
- 'android' => [
- 'priority' => 'high',
- ],
- 'apns' => [
- 'headers' => [
- 'apns-priority' => '10',
- ],
- 'payload' => [
- 'aps' => [
- 'sound' => 'default',
- 'badge' => 1,
- ],
- ],
- ],
- ],
- ];
- try {
- $response = Http::withToken($this->accessToken)->post($url, $message);
- $result = $response->json();
- Log::channel('fcm')->info('FCM send', [
- 'token' => substr($token, 0, 20) . '...',
- 'title' => $title,
- 'status' => $response->status(),
- 'response' => $result,
- ]);
- if ($response->failed()) {
- Log::channel('fcm')->error('FCM send failed', [
- 'token' => substr($token, 0, 20) . '...',
- 'status' => $response->status(),
- 'error' => $result,
- ]);
- }
- return $result;
- } catch (Throwable $e) {
- Log::channel('fcm')->error('FCM exception', [
- 'token' => substr($token, 0, 20) . '...',
- 'message' => $e->getMessage(),
- ]);
- return null;
- }
- }
- protected function getAccessToken(): string
- {
- $clientEmail = config('fcm.client_email');
- $privateKey = config('fcm.private_key');
- $jwt = $this->createJwt($clientEmail, $privateKey);
- return $this->exchangeJwtForAccessToken($jwt);
- }
- protected function createJwt(string $clientEmail, string $privateKey): string
- {
- $header = json_encode(['alg' => 'RS256', 'typ' => 'JWT']);
- $now = time();
- $payload = json_encode([
- 'iss' => $clientEmail,
- 'sub' => $clientEmail,
- 'aud' => 'https://oauth2.googleapis.com/token',
- 'iat' => $now,
- 'exp' => $now + 3600,
- 'scope' => 'https://www.googleapis.com/auth/cloud-platform',
- ]);
- $base64Header = $this->base64UrlEncode($header);
- $base64Payload = $this->base64UrlEncode($payload);
- $signature = '';
- openssl_sign("{$base64Header}.{$base64Payload}", $signature, $privateKey, 'sha256');
- return "{$base64Header}.{$base64Payload}." . $this->base64UrlEncode($signature);
- }
- protected function exchangeJwtForAccessToken(string $jwt): string
- {
- $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [
- 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
- 'assertion' => $jwt,
- ]);
- $data = $response->json();
- if (isset($data['access_token'])) {
- return $data['access_token'];
- }
- throw new \RuntimeException('FCM: Failed to obtain access token: ' . $response->body());
- }
- protected function base64UrlEncode(string $data): string
- {
- return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
- }
- }
|