Skip to content

Commit

Permalink
Merge pull request #72 from utopia-php/refactor-fcm
Browse files Browse the repository at this point in the history
Refactor FCM
  • Loading branch information
abnegate authored Dec 15, 2023
2 parents b2d4b03 + 299ac2e commit 64eca3f
Show file tree
Hide file tree
Showing 42 changed files with 700 additions and 349 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
MAILGUN_API_KEY=
MAILGUN_DOMAIN=
SENDGRID_API_KEY=
FCM_SERVER_KEY=
FCM_SERVICE_ACCOUNT_JSON=
FCM_TO=
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_TO=
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }}
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
FCM_SERVER_KEY: ${{ secrets.FCM_SERVER_KEY }}
FCM_SERVER_TO: ${{ secrets.FCM_SERVER_TO }}
FCM_SERVICE_ACCOUNT_JSON: ${{ secrets.FCM_SERVICE_ACCOUNT_JSON }}
FCM_TO: ${{ secrets.FCM_TO }}
TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }}
TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
TWILIO_TO: ${{ secrets.TWILIO_TO }}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ $message = new Push(
content: 'Hello World'
);

$messaging = new FCM('YOUR_SERVER_KEY');
$messaging = new FCM('YOUR_SERVICE_ACCOUNT_JSON');
$messaging->send($message);
```

Expand Down Expand Up @@ -108,7 +108,7 @@ $messaging->send($message);

### Push
- [x] [FCM](https://firebase.google.com/docs/cloud-messaging)
- [ ] [APNS](https://developer.apple.com/documentation/usernotifications)
- [x] [APNS](https://developer.apple.com/documentation/usernotifications)
- [ ] [OneSignal](https://onesignal.com/)
- [ ] [Pusher](https://pusher.com/)
- [ ] [WebPush](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
},
"require": {
"php": ">=8.0.0",
"ext-curl": "*"
"ext-curl": "*",
"ext-openssl": "*"
},
"require-dev": {
"ext-openssl": "*",
"phpunit/phpunit": "9.6.10",
"phpmailer/phpmailer": "6.8.*",
"laravel/pint": "1.13.*",
Expand Down
58 changes: 29 additions & 29 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,41 @@ version: '3.9'

services:
tests:
environment:
- MAILGUN_API_KEY
- MAILGUN_DOMAIN
- SENDGRID_API_KEY
- FCM_SERVER_KEY
- FCM_SERVER_TO
- TWILIO_ACCOUNT_SID
- TWILIO_AUTH_TOKEN
- TWILIO_TO
- TWILIO_FROM
- TELNYX_API_KEY
- TELNYX_PUBLIC_KEY
- APNS_AUTHKEY_8KVVCLA3HL
- APNS_AUTH_ID
- APNS_TEAM_ID
- APNS_BUNDLE_ID
- APNS_TO
- MSG_91_SENDER_ID
- MSG_91_AUTH_KEY
- MSG_91_TO
- MSG_91_FROM
- TEST_EMAIL
- TEST_FROM_EMAIL
- VONAGE_API_KEY
- VONAGE_API_SECRET
- VONAGE_TO
- VONAGE_FROM
- DISCORD_WEBHOOK_ID
- DISCORD_WEBHOOK_TOKEN
build:
context: .
volumes:
- ./src:/usr/local/src/src
- ./tests:/usr/local/src/tests
- ./phpunit.xml:/usr/local/src/phpunit.xml
environment:
- MAILGUN_API_KEY
- MAILGUN_DOMAIN
- SENDGRID_API_KEY
- FCM_SERVICE_ACCOUNT_JSON
- FCM_TO
- TWILIO_ACCOUNT_SID
- TWILIO_AUTH_TOKEN
- TWILIO_TO
- TWILIO_FROM
- TELNYX_API_KEY
- TELNYX_PUBLIC_KEY
- APNS_AUTHKEY_8KVVCLA3HL
- APNS_AUTH_ID
- APNS_TEAM_ID
- APNS_BUNDLE_ID
- APNS_TO
- MSG_91_SENDER_ID
- MSG_91_AUTH_KEY
- MSG_91_TO
- MSG_91_FROM
- TEST_EMAIL
- TEST_FROM_EMAIL
- VONAGE_API_KEY
- VONAGE_API_SECRET
- VONAGE_TO
- VONAGE_FROM
- DISCORD_WEBHOOK_ID
- DISCORD_WEBHOOK_TOKEN

maildev:
image: appwrite/mailcatcher:1.0.0
Expand Down
19 changes: 16 additions & 3 deletions docs/add-new-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,25 @@ public function process(Email $message): string
}
```

The base `Adapter` class includes a helper method called `request()` that can be used to send HTTP requests to the messaging provider. It accepts the following parameters, and returns the response as a string:
The base `Adapter` class includes a two helper functions called `request()` and `requestMulti()` that can be used to send HTTP requests to the messaging provider.

The `request()` function will send a single request and accepts the following parameters:

- `method` - The HTTP method to use. For example, `POST`, `GET`, `PUT`, `PATCH` or `DELETE`.
- `url` - The URL to send the request to.
- `headers` - An array of headers to send with the request.
- `body` - The body of the request. It can be either a string or an array.
- `body` - The body of the request as a string, or null if no body is required.
- `timeout` - The timeout in seconds for the request.

The `requestMulti()` function will send multiple concurrent requests via HTTP/2 multiplexing, and accepts the following parameters:

- `method` - The HTTP method to use. For example, `POST`, `GET`, `PUT`, `PATCH` or `DELETE`.
- `urls` - An array of URLs to send the requests to.
- `headers` - An array of headers to send with the requests.
- `bodies` - An array of bodies of the requests as strings, or an empty array if no body is required.
- `timeout` - The timeout in seconds for the requests.

`urls` and `bodies` must either be the same length, or one of them must contain only a single element. If `urls` contains only a single element, it will be used for all requests. If `bodies` contains only a single element, it will be used for all requests.

The default content type of the request is `x-www-form-urlencoded`, but you can change it by adding a `Content-Type` header. No encoding is applied to the body, so you need to make sure it is encoded properly before sending the request.

Expand Down Expand Up @@ -144,7 +157,7 @@ If everything goes well, raise a pull request and be ready to respond to any fee

## 4. Raise a pull request

First of all, commit the changes with the message `Added YYY Storage adapter` and push it. This will publish a new branch to your forked version of `utopia-php/messaging`. If you visit it at `github.com/YOUR_USERNAME/messaging`, you will see a new alert saying you are ready to submit a pull request. Follow the steps GitHub provides, and at the end, you will have your pull request submitted.
First of all, commit the changes with the message `Added YYY adapter` and push it. This will publish a new branch to your forked version of `utopia-php/messaging`. If you visit it at `github.com/YOUR_USERNAME/messaging`, you will see a new alert saying you are ready to submit a pull request. Follow the steps GitHub provides, and at the end, you will have your pull request submitted.

## 🤕 Stuck ?
If you need any help with the contribution, feel free to head over to [our discord channel](https://appwrite.io/discord) and we'll be happy to help you out.
168 changes: 146 additions & 22 deletions src/Utopia/Messaging/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ abstract public function getMaxMessagesPerRequest(): int;
/**
* Send a message.
*
* @param Message $message The message to send.
* @return string The response body.
* @return array{
* deliveredTo: int,
* type: string,
* results: array<array<string, mixed>>
* } | array<string, array{
* deliveredTo: int,
* type: string,
* results: array<array<string, mixed>>
* }>
*
* @throws \Exception If the message fails.
* @throws \Exception
*/
public function send(Message $message): string
public function send(Message $message): array
{
if (! \is_a($message, $this->getMessageType())) {
throw new \Exception('Invalid message type.');
Expand All @@ -48,13 +55,19 @@ public function send(Message $message): string
}

/**
* Send an HTTP request.
* Send a single HTTP request.
*
* @param string $method The HTTP method to use.
* @param string $url The URL to send the request to.
* @param array<string> $headers An array of headers to send with the request.
* @param string|null $body The body of the request.
* @return array<string, mixed> The response body.
* @param int $timeout The timeout in seconds.
* @return array{
* url: string,
* statusCode: int,
* response: array<string, mixed>|null,
* error: string|null
* }
*
* @throws \Exception If the request fails.
*/
Expand All @@ -63,6 +76,7 @@ protected function request(
string $url,
array $headers = [],
string $body = null,
int $timeout = 30
): array {
$ch = \curl_init();

Expand All @@ -71,31 +85,141 @@ protected function request(
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}

\curl_setopt($ch, CURLOPT_URL, $url);
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
\curl_setopt($ch, CURLOPT_USERAGENT, "Appwrite {$this->getName()} Message Sender");
\curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => "Appwrite {$this->getName()} Message Sender",
CURLOPT_TIMEOUT => $timeout,
]);

$response = \curl_exec($ch);

\curl_close($ch);

if (\curl_errno($ch)) {
throw new \Exception('Error: '.\curl_error($ch));
try {
$response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException) {
// Ignore
}

return [
'url' => $url,
'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE),
'response' => $response,
'error' => \curl_error($ch),
];
}

/**
* Send multiple concurrent HTTP requests using HTTP/2 multiplexing.
*
* @param array<string> $urls
* @param array<string> $headers
* @param array<string> $bodies
* @return array<array{
* url: string,
* statusCode: int,
* response: array<string, mixed>|null,
* error: string|null
* }>
*
* @throws \Exception
*/
protected function requestMulti(
string $method,
array $urls,
array $headers = [],
array $bodies = [],
int $timeout = 30
): array {
if (empty($urls)) {
throw new \Exception('No URLs provided. Must provide at least one URL.');
}

$sh = \curl_share_init();
$mh = \curl_multi_init();
$ch = \curl_init();

\curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
\curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);

\curl_setopt_array($ch, [
CURLOPT_SHARE => $sh,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FORBID_REUSE => false,
CURLOPT_FRESH_CONNECT => false,
CURLOPT_TIMEOUT => $timeout,
]);

$urlCount = \count($urls);
$bodyCount = \count($bodies);

if (
$urlCount != $bodyCount &&
($urlCount == 1 && $bodyCount != 1 || $urlCount != 1 && $bodyCount == 1)
) {
throw new \Exception('URL and body counts must be equal or 1.');
}

if ($urlCount > $bodyCount) {
$bodies = \array_pad($bodies, $urlCount, $bodies[0]);
} elseif ($urlCount < $bodyCount) {
$urls = \array_pad($urls, $bodyCount, $urls[0]);
}

$jsonResponse = \json_decode($response, true);
foreach (\array_combine($urls, $bodies) as $url => $body) {
if (! empty($body)) {
$headers[] = 'Content-Length: '.\strlen($body);
}

\curl_setopt($ch, CURLOPT_URL, $url);
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
\curl_multi_add_handle($mh, \curl_copy_handle($ch));
}

$active = true;
do {
$status = \curl_multi_exec($mh, $active);

if ($active) {
\curl_multi_select($mh);
}
} while ($active && $status == CURLM_OK);

if (\json_last_error() == JSON_ERROR_NONE) {
return [
'response' => $jsonResponse,
'statusCode' => \curl_getinfo($ch, CURLINFO_HTTP_CODE),
$responses = [];

// Check each handle's result
while ($info = \curl_multi_info_read($mh)) {
$ch = $info['handle'];

$response = \curl_multi_getcontent($ch);

try {
$response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException) {
// Ignore
}

$responses[] = [
'url' => \curl_getinfo($ch, CURLINFO_EFFECTIVE_URL),
'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE),
'response' => $response,
'error' => \curl_error($ch),
];

\curl_multi_remove_handle($mh, $ch);
\curl_close($ch);
}

return [
'response' => $response,
'statusCode' => \curl_getinfo($ch, CURLINFO_HTTP_CODE),
];
\curl_multi_close($mh);
\curl_share_close($sh);

return $responses;
}
}
9 changes: 7 additions & 2 deletions src/Utopia/Messaging/Adapter/Chat/Discord.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ public function getMaxMessagesPerRequest(): int
return 1;
}

protected function process(DiscordMessage $message): string
/**
* @return array{deliveredTo: int, type: string, results: array<array<string, mixed>>}
*
* @throws \Exception
*/
protected function process(DiscordMessage $message): array
{
$query = [];

Expand Down Expand Up @@ -89,6 +94,6 @@ protected function process(DiscordMessage $message): string
$response->addResultForRecipient($this->webhookId, 'Unknown Error.');
}

return \json_encode($response->toArray());
return $response->toArray();
}
}
Loading

0 comments on commit 64eca3f

Please sign in to comment.