From 926383b52ccfc11b8527d9d4573c827d0fd40887 Mon Sep 17 00:00:00 2001 From: Mufthi Ryanda <77824812+mufthiryanda@users.noreply.github.com> Date: Fri, 29 May 2026 16:37:20 +0700 Subject: [PATCH 1/3] feat : add sendToTopic --- src/Libraries/FirebaseClient.php | 54 +++++++++++++++ tests/Libraries/FirebaseClientTest.php | 95 ++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/src/Libraries/FirebaseClient.php b/src/Libraries/FirebaseClient.php index aaee89c..3103010 100644 --- a/src/Libraries/FirebaseClient.php +++ b/src/Libraries/FirebaseClient.php @@ -365,6 +365,60 @@ public function sendMulticast( return $results; } + /** + * Send FCM message to a topic + * + * @param string $topic Topic name (without /topics/ prefix) + * @param array $notification Notification payload (title, body) + * @param array $data Data payload + * + * @return ResponseInterface + * + * @throws \GuzzleHttp\Exception\GuzzleException On HTTP error + */ + public function sendToTopic( + string $topic, + array $notification = [], + array $data = [] + ): ResponseInterface { + $message = ['topic' => $topic]; + if (!empty($notification)) { + $message['notification'] = $notification; + } + if (!empty($data)) { + $message['data'] = $data; + } + + return $this->sendMessage($message); + } + + /** + * Send FCM message to a topic condition + * + * @param string $condition Topic condition expression (e.g. "'topicA' in topics && 'topicB' in topics") + * @param array $notification Notification payload (title, body) + * @param array $data Data payload + * + * @return ResponseInterface + * + * @throws \GuzzleHttp\Exception\GuzzleException On HTTP error + */ + public function sendToCondition( + string $condition, + array $notification = [], + array $data = [] + ): ResponseInterface { + $message = ['condition' => $condition]; + if (!empty($notification)) { + $message['notification'] = $notification; + } + if (!empty($data)) { + $message['data'] = $data; + } + + return $this->sendMessage($message); + } + /** * Generate JWT manually using OpenSSL * diff --git a/tests/Libraries/FirebaseClientTest.php b/tests/Libraries/FirebaseClientTest.php index 32927c2..5bf0201 100644 --- a/tests/Libraries/FirebaseClientTest.php +++ b/tests/Libraries/FirebaseClientTest.php @@ -273,6 +273,101 @@ public function testSendMulticastWithData(): void $this->assertEquals(0, $result['failure']); } + /** @test */ + public function testSendToTopic(): void + { + $mockResponse = new Response( + 200, + [], + json_encode(['name' => 'projects/test-project/messages/123']) + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $response = $client->sendToTopic( + 'news', + ['title' => 'Breaking', 'body' => 'Something happened'], + ['key' => 'value'] + ); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** @test */ + public function testSendToTopicWithNotificationOnly(): void + { + $mockResponse = new Response( + 200, + [], + json_encode(['name' => 'projects/test-project/messages/123']) + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $response = $client->sendToTopic('news', ['title' => 'Test', 'body' => 'Body']); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** @test */ + public function testSendToCondition(): void + { + $mockResponse = new Response( + 200, + [], + json_encode(['name' => 'projects/test-project/messages/123']) + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $response = $client->sendToCondition( + "'news' in topics || 'alerts' in topics", + ['title' => 'Update', 'body' => 'New update available'], + ['version' => '2.0'] + ); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** @test */ + public function testSendToConditionWithNotificationOnly(): void + { + $mockResponse = new Response( + 200, + [], + json_encode(['name' => 'projects/test-project/messages/123']) + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $response = $client->sendToCondition( + "'news' in topics && 'premium' in topics", + ['title' => 'Premium News', 'body' => 'Exclusive content'] + ); + + $this->assertEquals(200, $response->getStatusCode()); + } + /** @test */ public function testSetProxy(): void { From 7c1c6c8f66b219c775ca7cf24a632b0d26bba3b6 Mon Sep 17 00:00:00 2001 From: Mufthi Ryanda <77824812+mufthiryanda@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:22:23 +0700 Subject: [PATCH 2/3] feat : add conventional base64 handler --- src/Libraries/FirebaseClient.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Libraries/FirebaseClient.php b/src/Libraries/FirebaseClient.php index 3103010..5ece59c 100644 --- a/src/Libraries/FirebaseClient.php +++ b/src/Libraries/FirebaseClient.php @@ -429,8 +429,8 @@ public function sendToCondition( */ private function createJWT(array $payload, string $privateKey): string { - $header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])); - $payload = base64_encode(json_encode($payload)); + $header = rtrim(strtr(base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])), '+/', '-_'), '='); + $payload = rtrim(strtr(base64_encode(json_encode($payload)), '+/', '-_'), '='); $signature = ''; openssl_sign( @@ -440,8 +440,8 @@ private function createJWT(array $payload, string $privateKey): string OPENSSL_ALGO_SHA256 ); - $signature = base64_encode($signature); + $signature = rtrim(strtr(base64_encode($signature), '+/', '-_'), '='); - return str_replace(['+', '/', '='], ['-', '_', ''], $header . '.' . $payload . '.' . $signature); + return $header . '.' . $payload . '.' . $signature; } -} +} \ No newline at end of file From 53d2bd8ca3ab2ffbc1128c1d8f6fc3a5af5dcc9b Mon Sep 17 00:00:00 2001 From: Mufthi Ryanda <77824812+mufthiryanda@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:04:05 +0700 Subject: [PATCH 3/3] fix : fix phpcs --- src/Libraries/FirebaseClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/FirebaseClient.php b/src/Libraries/FirebaseClient.php index 5ece59c..028c122 100644 --- a/src/Libraries/FirebaseClient.php +++ b/src/Libraries/FirebaseClient.php @@ -444,4 +444,4 @@ private function createJWT(array $payload, string $privateKey): string return $header . '.' . $payload . '.' . $signature; } -} \ No newline at end of file +}