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, true); 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(); $image = $message->getImage(); if ($title !== '') { $payload['message']['notification']['title'] = $title; } if ($body !== '') { $payload['message']['notification']['body'] = $body; } if ($image !== '') { $payload['message']['notification']['image'] = $image; } $metadata = $message->getMetadata(); if (count($metadata)) { $metadata['sound'] = isset($metadata['sound']) ? $metadata['sound'] : 'default'; $payload['message']['android']['notification'] = $metadata; $payload['message']['webpush']['notification'] = $metadata; $payload['message']['apns']['payload'] = [ 'aps' => [ 'alert' => ['title' => $title, 'body' => $body], 'sound' => $metadata['sound'], 'badge' => $metadata['badge'] ?? 0, '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 = $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', 'settings' => [ 'auth' => 'google_auth', 'base_uri' => self::BASE_URL, 'handler' => $stack, 'json' => $payload, ], ]; $requests[$i]['settings']['json']['message']['token'] = $token; } $responses = HTTP::multiple($requests); foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); $result_text = $response->getBody()->getContents(); $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$tokens[$i]] = $result->name ?? ''; } elseif ($result && isset($result->error)) { $error_message = $result->error->message ?? ($result->error->status ?? ''); $message->addError('FCM error: HTTP ' . $status_code . ' ' . $error_message); if (str_contains($error_message, 'not a valid FCM registration token')) { $output->invalid[$tokens[$i]] = $tokens[$i]; } elseif (str_contains($error_message, 'Requested entity was not found')) { $output->invalid[$tokens[$i]] = $tokens[$i]; } } else { $message->addError('FCM error: HTTP ' . $status_code . ' ' . $response->getReasonPhrase()); } } } // Send a notification to each topic. $topics = $message->getTopics(); if (count($topics)) { $requests = []; foreach ($topics as $i => $topic) { $requests[$i] = [ 'url' => $api_url, 'method' => 'POST', 'settings' => [ 'auth' => 'google_auth', 'base_uri' => self::BASE_URL, 'handler' => $stack, 'json' => $payload, ], ]; $requests[$i]['settings']['json']['message']['topic'] = $topic; } $responses = HTTP::multiple($requests); foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); $result_text = $response->getBody()->getContents(); $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$topics[$i]] = $result->name ?? ''; } else { $error_message = $result->error->message ?? ($result->error->status ?? $response->getReasonPhrase()); $message->addError('FCM error: HTTP ' . $status_code . ' ' . $error_message); } } } return $output; } }