From 057bdfc233c1e12e3a328e5b6b720ae0ee461e27 Mon Sep 17 00:00:00 2001 From: Adam Campbell Date: Thu, 19 Oct 2023 07:37:11 -0500 Subject: [PATCH 1/4] Add support for multiple events --- README.md | 35 ++++++++++++++++++++++++++++++ src/Providers/AbstractProvider.php | 28 +++++++++++++++--------- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dc53229..25a4ff4 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,41 @@ The *`getEvent()`* method is used to return the name of the webhook event, ie. ` The *`getData()`* method is used to return the payload of data that can be used within your handler. By default this is set to `$request->all()`. +#### Receiving multiple events in a single webhook + +Sometimes the services will send multiple event payloads in a single webhook. In that case, you may return an array of events from the `getEvent` method and Receiver will handle them each individually. + +For example, if the payload looks like this: +```json +{ + "time_ms": 1697717045179, + "events": [ + { + "name": "channel_occupied", + "channel": "admin", + "data": {} + }, + { + "name": "member_added", + "channel": "admin", + "data": {} + } + ] +} +``` + +You may return the events from the `getEvent` method like so: +```php +public function getEvent(): array +{ + return [ + 'channel_occupied', + 'member_added', + ]; +} +``` +Receiver will then handle each event individually. + ### Securing Webhooks Many webhooks have ways of verifying their authenticity as they are received, most commonly through signatures or basic authentication. No matter the strategy, Receiver allows you to write custom verification code as necessary. Simply implement the `verify` method in your provider and return true or false if it passes. diff --git a/src/Providers/AbstractProvider.php b/src/Providers/AbstractProvider.php index 27ecaa4..6cd1ab9 100644 --- a/src/Providers/AbstractProvider.php +++ b/src/Providers/AbstractProvider.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Illuminate\Support\Str; use Receiver\Contracts\Provider as ProviderContract; use Symfony\Component\HttpFoundation\Response; @@ -35,9 +36,9 @@ abstract class AbstractProvider implements ProviderContract, Responsable protected Closure|null $fallback = null; /** - * @var bool + * @var array */ - protected mixed $dispatched = false; + protected mixed $dispatchedEvents = []; /** * @var string @@ -53,9 +54,9 @@ public function __construct(protected ?string $secret = null) /** * @param Request $request - * @return string + * @return string|array */ - abstract public function getEvent(Request $request): string; + abstract public function getEvent(Request $request): string|array; /** * @param Request $request @@ -138,11 +139,14 @@ public function webhook(): ?Webhook } /** + * @param string|null $key * @return bool */ - public function dispatched(): bool + public function dispatched(string $key = null): bool { - return $this->dispatched; + return $key + ? in_array($key, $this->dispatchedEvents) + : ! empty($this->dispatchedEvents); } /** @@ -162,12 +166,16 @@ protected function mapWebhook(Request $request): Webhook */ protected function handle(): static { - $class = $this->getClass($event = $this->webhook->getEvent()); + $events = Arr::wrap($this->webhook->getEvent()); + + foreach($events as $event) { + $class = $this->getClass($event = $this->webhook->getEvent()); - if (class_exists($class)) { - $class::dispatch($event, $this->webhook->getData()); + if (class_exists($class)) { + $class::dispatch($event, $this->webhook->getData()); - $this->dispatched = true; + $this->dispatchedEvents[] = $class; + } } return $this; From 9479155bf9bb2b5551020dcb6412eb70a0a55eae Mon Sep 17 00:00:00 2001 From: Adam Campbell Date: Thu, 19 Oct 2023 07:38:13 -0500 Subject: [PATCH 2/4] Update AbstractProvider.php --- src/Providers/AbstractProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Providers/AbstractProvider.php b/src/Providers/AbstractProvider.php index 6cd1ab9..f0a81d9 100644 --- a/src/Providers/AbstractProvider.php +++ b/src/Providers/AbstractProvider.php @@ -169,7 +169,7 @@ protected function handle(): static $events = Arr::wrap($this->webhook->getEvent()); foreach($events as $event) { - $class = $this->getClass($event = $this->webhook->getEvent()); + $class = $this->getClass($event); if (class_exists($class)) { $class::dispatch($event, $this->webhook->getData()); From 6602c653003610fd0a67a1f61ec594880e7a8535 Mon Sep 17 00:00:00 2001 From: Adam Campbell Date: Thu, 19 Oct 2023 07:43:17 -0500 Subject: [PATCH 3/4] Update php.yml --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8e103ad..4f11c73 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -17,7 +17,7 @@ jobs: php: [ 8.1, 8.2 ] include: - laravel: 9.* - testbench: 6.* + testbench: 7.* - laravel: 10.* testbench: 8.* From 2a6893c46ca2e954e9f83579455c17838e1bb483 Mon Sep 17 00:00:00 2001 From: Adam Campbell Date: Thu, 19 Oct 2023 07:51:04 -0500 Subject: [PATCH 4/4] Fix handler --- README.md | 16 +++++++++++----- phpunit.xml.dist | 29 +++++++++++++---------------- phpunit.xml.dist.bak | 17 +++++++++++++++++ src/Providers/AbstractProvider.php | 10 +++++++--- 4 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 phpunit.xml.dist.bak diff --git a/README.md b/README.md index 25a4ff4..a5e1e0b 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,9 @@ The *`getData()`* method is used to return the payload of data that can be used #### Receiving multiple events in a single webhook -Sometimes the services will send multiple event payloads in a single webhook. In that case, you may return an array of events from the `getEvent` method and Receiver will handle them each individually. +Sometimes the services will send multiple event payloads in a single webhook. + +In this scenario you may return an array of mapped events from the `getEvent` method and Receiver will handle them each individually. For example, if the payload looks like this: ```json @@ -371,10 +373,14 @@ You may return the events from the `getEvent` method like so: ```php public function getEvent(): array { - return [ - 'channel_occupied', - 'member_added', - ]; + return $events = $this->request + ->collect('events') + ->mapToGroups( + fn ($item) => [ + $item['name'] => $item + ] + ) + ->toArray(); } ``` Receiver will then handle each event individually. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b640754..02ecaec 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,14 @@ - - - - src - - - - - tests - - - \ No newline at end of file + + + + + tests + + + + + src + + + diff --git a/phpunit.xml.dist.bak b/phpunit.xml.dist.bak new file mode 100644 index 0000000..b640754 --- /dev/null +++ b/phpunit.xml.dist.bak @@ -0,0 +1,17 @@ + + + + + src + + + + + tests + + + \ No newline at end of file diff --git a/src/Providers/AbstractProvider.php b/src/Providers/AbstractProvider.php index f0a81d9..434ab6d 100644 --- a/src/Providers/AbstractProvider.php +++ b/src/Providers/AbstractProvider.php @@ -166,13 +166,17 @@ protected function mapWebhook(Request $request): Webhook */ protected function handle(): static { - $events = Arr::wrap($this->webhook->getEvent()); + $events = $this->webhook->getEvent(); - foreach($events as $event) { + if(! is_array($events)) { + $events = [$events => $this->webhook->getData()]; + } + + foreach($events as $event => $data) { $class = $this->getClass($event); if (class_exists($class)) { - $class::dispatch($event, $this->webhook->getData()); + $class::dispatch($event, $data); $this->dispatchedEvents[] = $class; }