where('name', 'firebase_project_id')->first(); if (empty($project_id->value)) { return ['error' => true, 'message' => 'FCM configurations are not configured.']; } $project_id = $project_id->value; $url = 'https://fcm.googleapis.com/v1/projects/' . $project_id . '/messages:send'; $access_token = self::getAccessToken(); if ($access_token['error']) { return $access_token; } $dataWithTitle = [ ...$customBodyFields, "title" => $title, "body" => $message, "type" => $type, ]; // ✅ Case 1: Send to all (topic-based) if ($sendToAll) { $data = [ "message" => [ "topic" => "allUsers", // universal topic (subscribe everyone here) "data" => self::convertToStringRecursively($dataWithTitle), "notification" => [ "title" => $title, "body" => $message ] ] ]; $encodedData = json_encode($data); $headers = [ 'Authorization: Bearer ' . $access_token['data'], 'Content-Type: application/json', ]; $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, 2); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData); $result = curl_exec($ch); if (!$result) { return ['error' => true, 'message' => 'Curl failed: ' . curl_error($ch)]; } curl_close($ch); return ['error' => false, 'message' => "Bulk notification sent via topic", 'data' => $result]; } // ✅ Case 2: Send individually (like existing code) $deviceInfo = UserFcmToken::with('user') ->select(['platform_type', 'fcm_token']) ->whereIn('fcm_token', $registrationIDs) ->whereHas('user', fn($q) => $q->where('notification', 1)) ->get(); // ✅ Log all targeted users and their FCM tokens before sending try { // Log::info('🔔 Device Info for Notification', $deviceInfo->toArray()); } catch (Throwable $e) { Log::error('Failed to log device info: ' . $e->getMessage()); } $result = []; foreach ($registrationIDs as $registrationID) { $platform = $deviceInfo->first(fn($q) => $q->fcm_token == $registrationID); if (!$platform) continue; $data = [ 'message' => [ 'token' => $registrationID, // Notification block (title & body) 'notification' => [ 'title' => $title, 'body' => $message, ], // Custom data (ALL values must be strings) 'data' => self::convertToStringRecursively($dataWithTitle), // Android config 'android' => [ 'priority' => 'high', 'notification' => [ 'image' => !empty($dataWithTitle['image']) ? $dataWithTitle['image'] : null, ], ], // iOS (APNs) config 'apns' => [ 'headers' => [ 'apns-priority' => '10', ], 'payload' => [ 'aps' => [ 'alert' => [ 'title' => $title, 'body' => $message, ], 'sound' => 'default', 'mutable-content' => 1, // REQUIRED for images ], ], 'fcm_options' => [ 'image' => !empty($dataWithTitle['image']) ? $dataWithTitle['image'] : null, ], ], ], ]; $encodedData = json_encode($data); $headers = [ 'Authorization: Bearer ' . $access_token['data'], 'Content-Type: application/json', ]; $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, 2); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData); $result[] = curl_exec($ch); curl_close($ch); } return ['error' => false, 'message' => "Individual notifications sent", 'data' => $result]; } catch (Throwable $th) { throw new RuntimeException($th); } } public static function getAccessToken() { try { $file_name = Setting::select('value')->where('name', 'service_file')->first(); if (empty($file_name)) { return [ 'error' => true, 'message' => 'FCM Configuration not found' ]; } $disk = config('filesystems.default'); $file = $file_name->value; if ($disk === 'local' || $disk === 'public') { // LOCAL STORAGE $file_path = Storage::disk($disk)->path($file); } else { // S3 (or any cloud disk) // Download file to local temp $fileContent = Storage::disk($disk)->get($file); $file_path = storage_path('app/firebase_service.json'); file_put_contents($file_path, $fileContent); } if (!file_exists($file_path)) { return [ 'error' => true, 'message' => 'FCM Service File not found' ]; } $client = new Client(); $client->setAuthConfig($file_path); $client->setScopes(['https://www.googleapis.com/auth/firebase.messaging']); return [ 'error' => false, 'message' => 'Access Token generated successfully', 'data' => $client->fetchAccessTokenWithAssertion()['access_token'] ]; } catch (Exception $e) { throw new RuntimeException($e); } } public static function convertToStringRecursively($data, &$flattenedArray = []) { foreach ($data as $key => $value) { if (is_array($value)) { self::convertToStringRecursively($value, $flattenedArray); } elseif (is_null($value)) { $flattenedArray[$key] = ''; } else { $flattenedArray[$key] = (string)$value; } } return $flattenedArray; } public static function sendNewDeviceLoginEmail(User $user, HttpRequest $request) { try { Log::info("sending New device login alert"); $deviceType = ucfirst($request->platform_type ?? 'Unknown'); $ip = request()->ip(); $loginTime = now()->format('d M Y - h:i A'); // Fetch company name $companyName = Setting::where('name', 'company_name')->value('value') ?? 'Unknown'; // Email message $message = "A new device has just logged in to your {$companyName} account.\n\n" . "⏰ Login Time: {$loginTime}\n\n"; Mail::raw($message, function ($msg) use ($user, $companyName) { $msg->to($user->email) ->from('admin@yourdomain.com', $companyName) ->subject("New Device Login Detected - {$companyName}"); }); Log::info("New device login alert sent to: {$user->email}"); } catch (\Exception $e) { Log::error("Failed to send new device login email: " . $e->getMessage()); } } }