Ver Fonte

fix: fcm fo apple, add fcm logging

Alexander Musikhin há 1 semana atrás
pai
commit
c0da737d0d

+ 23 - 0
app/Providers/FcmServiceProvider.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Providers;
+
+use App\Services\Fcm\FcmChannel;
+use App\Services\Fcm\FcmService;
+use Illuminate\Notifications\ChannelManager;
+use Illuminate\Support\ServiceProvider;
+
+class FcmServiceProvider extends ServiceProvider
+{
+    public function register(): void
+    {
+        $this->app->singleton(FcmService::class);
+    }
+
+    public function boot(): void
+    {
+        $this->app->make(ChannelManager::class)->extend('fcm', function ($app) {
+            return $app->make(FcmChannel::class);
+        });
+    }
+}

+ 28 - 0
app/Services/Fcm/FcmChannel.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Services\Fcm;
+
+use Illuminate\Notifications\Notification;
+
+class FcmChannel
+{
+    public function __construct(
+        protected FcmService $fcm,
+    ) {}
+
+    public function send(object $notifiable, Notification $notification): void
+    {
+        if (!method_exists($notification, 'toFcm')) {
+            return;
+        }
+
+        $data = $notification->toFcm($notifiable);
+
+        $this->fcm->send(
+            token: $data['to'],
+            title: $data['notification']['title'] ?? '',
+            body: $data['notification']['body'] ?? '',
+            image: $data['notification']['image'] ?? null,
+        );
+    }
+}

+ 132 - 0
app/Services/Fcm/FcmService.php

@@ -0,0 +1,132 @@
+<?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));
+    }
+}

+ 1 - 0
bootstrap/providers.php

@@ -6,4 +6,5 @@ return [
     App\Providers\AppServiceProvider::class,
     BroadcastServiceProvider::class,
     App\Providers\HelperServiceProvider::class,
+    App\Providers\FcmServiceProvider::class,
 ];

+ 3 - 1
composer.json

@@ -60,7 +60,9 @@
     },
     "extra": {
         "laravel": {
-            "dont-discover": []
+            "dont-discover": [
+                "syntech/syntechfcm"
+            ]
         }
     },
     "config": {

+ 7 - 0
config/fcm.php

@@ -0,0 +1,7 @@
+<?php
+
+return [
+    'project_id' => env('FCM_PROJECT_ID'),
+    'client_email' => env('FCM_CLIENT_EMAIL'),
+    'private_key' => env('FCM_PRIVATE_KEY'),
+];

+ 8 - 0
config/logging.php

@@ -127,6 +127,14 @@ return [
             'path' => storage_path('logs/laravel.log'),
         ],
 
+        'fcm' => [
+            'driver' => 'daily',
+            'path' => storage_path('logs/fcm.log'),
+            'level' => 'debug',
+            'days' => 14,
+            'replace_placeholders' => true,
+        ],
+
     ],
 
 ];