Skip to content

Commit cd192db

Browse files
committed
Compatibility with FCM HTTP v1 API
1 parent 4d28241 commit cd192db

23 files changed

+143
-435
lines changed

.github/workflows/ci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ jobs:
1616
- '8.0'
1717
- '8.1'
1818
- '8.2'
19+
- '8.3'
1920

2021
steps:
2122
- name: Checkout
22-
uses: actions/checkout@v3
23+
uses: actions/checkout@v4
2324

2425
- name: Setup PHP
2526
uses: shivammathur/setup-php@v2

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
55

66
## [Unreleased]
7+
### Added
8+
- Compatibility with FCM HTTP v1 API.
9+
10+
### Changed
11+
- Signature of the `\ErickSkrauch\Fcm\Client` class now requires the `projectId` param.
12+
- The return type of the `\ErickSkrauch\Fcm\Client::send()` method is changed to `string`.
13+
- `\ErickSkrauch\Fcm\Client::send()` now throws `\ErickSkrauch\Fcm\Exception\ErrorResponseException`.
14+
15+
### Removed
16+
- `ErickSkrauch\Fcm\Recipient\MultipleDevices` and `ErickSkrauch\Fcm\Recipient\MultipleTopics` recipients. Iterate over per-device or per-topic to archive the same behavior.
17+
- `ErickSkrauch\Fcm\Response\SendResponse`.
18+
19+
### Fixed
20+
- Deprecation error with nullable arguments for PHP 8.4.
721

822
## [0.2.1] - 2022-12-21
923
### Fixed

README.md

+2-26
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use ErickSkrauch\Fcm\Message\Message;
3434
use ErickSkrauch\Fcm\Message\Notification;
3535
use ErickSkrauch\Fcm\Recipient\Device;
3636

37-
$client = new Client('YOUR SERVER KEY');
37+
$client = new Client('SERVICE ACCOUNT OAUTH TOKEN', 'project-x146w');
3838

3939
$notification = new Notification(, 'testing body');
4040
$notification->setTitle('Wow, something happened...');
@@ -44,35 +44,11 @@ $message = new Message();
4444
$message->setNotification($notification);
4545
$message->setCollapseKey('collapse.key');
4646

47-
$recipient = new Device('your-device-token');
47+
$recipient = new Device('your-device-token'); // or new ErickSkrauch\Fcm\Recipient\Topic('topic');
4848

4949
$result = $client->send($message, $recipient);
5050
```
5151

52-
The library provides several implementations for the `Recipient` interface:
53-
* `Device` is used to send notifications to a single device.
54-
* `Topic` is used to send notifications to a single topic.
55-
* `MultipleDevices` is used to send notifications to multiple devices. The FCM documentation doesn't recommend using this method to send to 1 device.
56-
* `MultipleTopics` is used to send notifications to multiple topics combined by the operator `||`.
57-
58-
At the moment, the library does not have a builder for complex conditions. But you can always create your own implementation of the `Recipient` interface:
59-
60-
```php
61-
use ErickSkrauch\Fcm\Recipient\Recipient;
62-
63-
class MyComplexCondition implements Recipient {
64-
65-
public function getConditionParam(): string{
66-
return Recipient::PARAM_CONDITION;
67-
}
68-
69-
public function getConditionValue(): string {
70-
return "'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)";
71-
}
72-
73-
}
74-
```
75-
7652
## Contribute
7753

7854
This library in an Open Source under the MIT license. It is, thus, maintained by collaborators and contributors.

src/Client.php

+22-22
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
namespace ErickSkrauch\Fcm;
55

6+
use ErickSkrauch\Fcm\Exception\ErrorResponseException;
67
use ErickSkrauch\Fcm\Exception\UnexpectedResponseException;
78
use ErickSkrauch\Fcm\Message\Message;
89
use ErickSkrauch\Fcm\Recipient\Recipient;
9-
use ErickSkrauch\Fcm\Response\SendResponse;
1010
use Http\Discovery\Psr17FactoryDiscovery;
1111
use Http\Discovery\Psr18ClientDiscovery;
1212
use Psr\Http\Client\ClientInterface as HttpClientInterface;
@@ -17,52 +17,52 @@ final class Client implements ClientInterface {
1717

1818
private string $apiKey;
1919

20+
private string $projectName;
21+
2022
private HttpClientInterface $httpClient;
2123

2224
private HttpRequestFactoryInterface $requestFactory;
2325

2426
private StreamFactoryInterface $streamFactory;
2527

2628
/**
27-
* @param string $apiKey read how to obtain an api key here: https://firebase.google.com/docs/server/setup#prerequisites
28-
* @param HttpClientInterface|null $httpClient
29-
* @param HttpRequestFactoryInterface|null $requestFactory
30-
* @param StreamFactoryInterface|null $streamFactory
29+
* @param string $oauthToken you must generate the JWT key for your service account with a scope "https://www.googleapis.com/auth/firebase.messaging"
30+
* @param string $projectId you can find the project's ID here: https://console.firebase.google.com. Copy the gray text under the project name. For example "myapp-5427d"
3131
*/
3232
public function __construct(
33-
string $apiKey,
34-
HttpClientInterface $httpClient = null,
35-
HttpRequestFactoryInterface $requestFactory = null,
36-
StreamFactoryInterface $streamFactory = null
33+
string $oauthToken,
34+
string $projectId,
35+
?HttpClientInterface $httpClient = null,
36+
?HttpRequestFactoryInterface $requestFactory = null,
37+
?StreamFactoryInterface $streamFactory = null
3738
) {
38-
$this->apiKey = $apiKey;
39+
$this->apiKey = $oauthToken;
40+
$this->projectName = $projectId;
3941
$this->httpClient = $httpClient ?? Psr18ClientDiscovery::find();
4042
$this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory();
4143
$this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory();
4244
}
4345

44-
public function send(Message $message, Recipient $recipient): SendResponse {
46+
public function send(Message $message, Recipient $recipient): string {
4547
$json = $message->getPayload();
4648
$json[$recipient->getConditionParam()] = $recipient->getConditionValue();
4749

48-
$request = $this->requestFactory->createRequest('POST', 'https://fcm.googleapis.com/fcm/send');
49-
$request = $request->withHeader('Authorization', "key={$this->apiKey}");
50+
$request = $this->requestFactory->createRequest('POST', "https://fcm.googleapis.com/v1/projects/{$this->projectName}/messages:send");
51+
$request = $request->withHeader('Authorization', "Bearer {$this->apiKey}");
5052
$request = $request->withHeader('Content-Type', 'application/json');
51-
$request = $request->withBody($this->streamFactory->createStream(json_encode($json, JSON_THROW_ON_ERROR)));
53+
$request = $request->withBody($this->streamFactory->createStream(json_encode(['message' => $json], JSON_THROW_ON_ERROR)));
5254

5355
$response = $this->httpClient->sendRequest($request);
54-
if ($response->getStatusCode() !== 200) {
55-
throw new UnexpectedResponseException($response);
56+
if (!in_array($response->getStatusCode(), [200, 400], true)) {
57+
throw new UnexpectedResponseException("Received unexpected FCM response with {$response->getStatusCode()} status code", $response);
5658
}
5759

5860
$json = json_decode((string)$response->getBody(), true, JSON_THROW_ON_ERROR);
61+
if (isset($json['error'])) {
62+
throw new ErrorResponseException($json['error']['message'], $json['error']['status'], $response);
63+
}
5964

60-
return new SendResponse(
61-
$json['multicast_id'],
62-
$json['success'],
63-
$json['failure'],
64-
$json['results'],
65-
);
65+
return $json['name'];
6666
}
6767

6868
}

src/ClientInterface.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55

66
use ErickSkrauch\Fcm\Message\Message;
77
use ErickSkrauch\Fcm\Recipient\Recipient;
8-
use ErickSkrauch\Fcm\Response\SendResponse;
98

109
interface ClientInterface {
1110

1211
/**
1312
* Sends your notification to the FCM and returns a raw response
1413
*
15-
* @param Message $message
16-
* @param Recipient $recipient
14+
* @see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send
1715
*
18-
* @return SendResponse
16+
* @return string the send ID from FCM
1917
* @throws \Psr\Http\Client\ClientExceptionInterface
2018
* @throws \ErickSkrauch\Fcm\Exception\UnexpectedResponseException
19+
* @throws \ErickSkrauch\Fcm\Exception\ErrorResponseException
20+
* @throws \JsonException
2121
*/
22-
public function send(Message $message, Recipient $recipient): SendResponse;
22+
public function send(Message $message, Recipient $recipient): string;
2323

2424
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace ErickSkrauch\Fcm\Exception;
5+
6+
use Exception;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
final class ErrorResponseException extends Exception {
10+
11+
/**
12+
* @readonly
13+
*/
14+
public string $errorCode;
15+
16+
/**
17+
* @readonly
18+
*/
19+
public ResponseInterface $response;
20+
21+
public function __construct(string $message, string $errorCode, ResponseInterface $response) {
22+
$this->errorCode = $errorCode;
23+
$this->response = $response;
24+
parent::__construct($message, $response->getStatusCode());
25+
}
26+
27+
}

src/Exception/UnexpectedResponseException.php

+6-7
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88

99
final class UnexpectedResponseException extends Exception {
1010

11-
private ResponseInterface $response;
11+
/**
12+
* @readonly
13+
*/
14+
public ResponseInterface $response;
1215

13-
public function __construct(ResponseInterface $response) {
14-
parent::__construct('Received an unexpected response from FCM');
16+
public function __construct(string $message, ResponseInterface $response) {
1517
$this->response = $response;
16-
}
17-
18-
public function getResponse(): ResponseInterface {
19-
return $this->response;
18+
parent::__construct($message, $response->getStatusCode());
2019
}
2120

2221
}

src/Message/Message.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class Message {
1818
* mutable_content?: true,
1919
* content_available?: true,
2020
* image?: string,
21-
* data?: array<string, string|int>,
21+
* data?: string,
2222
* }
2323
*/
2424
private array $data = [];
@@ -87,10 +87,12 @@ public function setImage(string $imageUrl): self {
8787

8888
/**
8989
* @see https://firebase.google.com/docs/cloud-messaging/http-server-ref#data
90-
* @param array<string, string|int> $data
90+
* @param array<mixed> $data
9191
*/
9292
public function setData(array $data): self {
93-
$this->data['data'] = $data;
93+
// https://firebase.google.com/docs/cloud-messaging/migrate-v1#example-nested-json-data
94+
$this->data['data'] = json_encode($data, JSON_THROW_ON_ERROR);
95+
9496
return $this;
9597
}
9698

@@ -104,7 +106,7 @@ public function setData(array $data): self {
104106
* mutable_content?: true,
105107
* content_available?: true,
106108
* image?: string,
107-
* data?: array<string, string|int>,
109+
* data?: string,
108110
* }
109111
*/
110112
public function getPayload(): array {

src/Message/Notification.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function setSound(string $sound): self {
106106
* @param array<string|int>|null $args
107107
* @return self
108108
*/
109-
public function setTitleLocKey(string $titleLocKey, array $args = null): self {
109+
public function setTitleLocKey(string $titleLocKey, ?array $args = null): self {
110110
if (isset($this->data['title'])) {
111111
throw new InvalidArgumentException('You cannot use "title" and "title_loc_key" at the same time');
112112
}
@@ -124,7 +124,7 @@ public function setTitleLocKey(string $titleLocKey, array $args = null): self {
124124
* @param array<string|int>|null $args
125125
* @return self
126126
*/
127-
public function setBodyLocKey(string $bodyLocKey, array $args = null): self {
127+
public function setBodyLocKey(string $bodyLocKey, ?array $args = null): self {
128128
if (isset($this->data['body'])) {
129129
throw new InvalidArgumentException('You cannot use "body" and "body_loc_key" at the same time');
130130
}

src/Recipient/Device.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public function __construct(string $token) {
1212
}
1313

1414
public function getConditionParam(): string {
15-
return Recipient::PARAM_TO;
15+
return 'token';
1616
}
1717

1818
public function getConditionValue(): string {

src/Recipient/MultipleDevices.php

-37
This file was deleted.

src/Recipient/MultipleTopics.php

-34
This file was deleted.

0 commit comments

Comments
 (0)