From bc8c9f375c3ca887c9ae9e1e0b410c539fc80e8a Mon Sep 17 00:00:00 2001 From: Joe FRANCOIS Date: Wed, 14 Feb 2024 10:35:47 +0100 Subject: [PATCH 1/5] feat: adds tests and fix bug sending more than 100 notification to Expo API If you were sending more than 100 notifications (not batchable), it would failed because of the Expo Push limitation. This fix will chunk by 100 the notifications. For example, if there is 120 notifications, 2 chunks will be created and 2 requests will be sent to Expo API. There was no test suite set up, so I had to set this first then I just wrote the test to cover the bugfix. This package is missing tests, feel free to add more :) --- composer.json | 1 + phpunit.xml | 5 + .../ExpoNotificationsServiceInterface.php | 24 +++ src/ExpoNotificationsChannel.php | 3 +- src/ExpoNotificationsService.php | 162 ++++++++++----- src/ExpoNotificationsServiceProvider.php | 3 +- src/FakeExpoNotificationsService.php | 184 ++++++++++++++++++ src/Jobs/CheckTickets.php | 4 +- src/Jobs/SendPendingNotifications.php | 4 +- tests/Feature/ExpoNotificationServiceTest.php | 35 ++++ .../ExpoPendingNotificationStorageTest.php | 46 +++++ tests/TestCase.php | 50 ++++- 12 files changed, 464 insertions(+), 57 deletions(-) create mode 100644 src/Contracts/ExpoNotificationsServiceInterface.php create mode 100644 src/FakeExpoNotificationsService.php create mode 100644 tests/Feature/ExpoNotificationServiceTest.php create mode 100644 tests/Feature/Storage/ExpoPendingNotificationStorageTest.php diff --git a/composer.json b/composer.json index bd666fc..982d728 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "require-dev": { "ciareis/bypass": "^1.0", "dg/bypass-finals": "^1.4", + "guzzlehttp/guzzle": "^7.8", "laravel/pint": "^1.3", "orchestra/testbench": "7.*|8.*", "pestphp/pest": "^1.21", diff --git a/phpunit.xml b/phpunit.xml index a86411a..57cf6e7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,4 +19,9 @@ src/ + + + + + diff --git a/src/Contracts/ExpoNotificationsServiceInterface.php b/src/Contracts/ExpoNotificationsServiceInterface.php new file mode 100644 index 0000000..6751df2 --- /dev/null +++ b/src/Contracts/ExpoNotificationsServiceInterface.php @@ -0,0 +1,24 @@ + 'gzip, deflate', 'content-type' => 'application/json', ])->baseUrl($apiUrl); + + $this->tickets = collect(); } /** * @param ExpoMessage|ExpoMessage[]|Collection $expoMessages * @return Collection - * - * @throws ExpoNotificationsException */ public function notify(ExpoMessage|Collection|array $expoMessages): Collection { /** @var Collection $expoMessages */ - $expoMessages = $expoMessages instanceof Collection ? $expoMessages : collect(Arr::wrap($expoMessages)); - - $shouldBatchFilter = fn (ExpoMessage $message) => $message->shouldBatch; - - // Store notifications to send in the next batch - $expoMessages - ->filter($shouldBatchFilter) - ->each(fn (ExpoMessage $message) => $this->notificationStorage->store($message)); - - // Filter notifications to send now - $toSend = $expoMessages - ->reject($shouldBatchFilter) - ->map(fn (ExpoMessage $message) => $message->toExpoData()) - ->values(); - - if ($toSend->isEmpty()) { - return collect(); - } - - $response = $this->http->post('/send', $toSend->toArray()); - if (! $response->successful()) { - throw new ExpoNotificationsException($response->toPsrResponse()); - } - - $data = json_decode($response->body(), true); - if (! empty($data['errors'])) { - throw new ExpoNotificationsException($response->toPsrResponse()); - } - - $tickets = collect($data['data'])->map(function ($responseItem) { - if ($responseItem['status'] === ExpoResponseStatus::ERROR->value) { - return (new PushTicketResponse()) - ->status($responseItem['status']) - ->message($responseItem['message']) - ->details($responseItem['details']); - } - - return (new PushTicketResponse()) - ->status($responseItem['status']) - ->ticketId($responseItem['id']); - }); + $this->expoMessages = $expoMessages instanceof Collection ? $expoMessages : collect(Arr::wrap($expoMessages)); - $this->checkAndStoreTickets($toSend->pluck('to')->flatten(), $tickets); - - return $tickets; + return $this->storeNotificationsToSendInTheNextBatch() + ->prepareNotificationsToSendNow() + ->sendNotifications(); } /** @@ -130,13 +104,17 @@ public function receipts(Collection|array $tokenIds): Collection }); } + public function getNotificationChunks(): Collection + { + return $this->notificationChunks ?? collect(); + } + /** * @param Collection $tokens - * @param Collection $tickets */ - private function checkAndStoreTickets(Collection $tokens, Collection $tickets): void + private function checkAndStoreTickets(Collection $tokens): void { - $tickets + $this->tickets ->intersectByKeys($tokens) ->each(function (PushTicketResponse $ticket, $index) use ($tokens) { if ($ticket->status === ExpoResponseStatus::ERROR->value) { @@ -152,4 +130,88 @@ private function checkAndStoreTickets(Collection $tokens, Collection $tickets): } }); } + + private function storeNotificationsToSendInTheNextBatch(): ExpoNotificationsService + { + $this->expoMessages + ->filter(fn (ExpoMessage $message) => $message->shouldBatch) + ->each(fn (ExpoMessage $message) => $this->notificationStorage->store($message)); + + return $this; + } + + private function prepareNotificationsToSendNow(): ExpoNotificationsService + { + $this->notificationsToSend = $this->expoMessages + ->reject(fn (ExpoMessage $message) => $message->shouldBatch) + ->map(fn (ExpoMessage $message) => $message->toExpoData()) + ->values(); + + // Splits into multiples chunks of max limitation + $this->notificationChunks = $this->notificationsToSend->chunk(self::PUSH_NOTIFICATIONS_PER_REQUEST_LIMIT); + + $this->checkAndStoreTickets($this->notificationsToSend->pluck('to')->flatten()); + + return $this; + } + + private function sendNotifications(): Collection + { + if ($this->notificationsToSend->isEmpty()) { + return collect(); + } + + $this->notificationChunks + ->each( + fn ($chunk, $index) => $this->sendNotificationsChunk($chunk->toArray()) + ); + + $this->checkAndStoreTickets($this->notificationsToSend->pluck('to')->flatten()); + + return $this->tickets; + } + + private function handleSendNotificationsResponse(Response $response): void + { + $data = json_decode($response->body(), true, 512, JSON_THROW_ON_ERROR); + if (! empty($data['errors'])) { + throw new ExpoNotificationsException($response->toPsrResponse()); + } + + $this->setTicketsFromData($data); + } + + private function setTicketsFromData(array $data): ExpoNotificationsService + { + collect($data['data']) + ->each(function ($responseItem) { + if ($responseItem['status'] === ExpoResponseStatus::ERROR->value) { + $this->tickets->push( + (new PushTicketResponse()) + ->status($responseItem['status']) + ->message($responseItem['message']) + ->details($responseItem['details']) + ); + } else { + $this->tickets->push( + (new PushTicketResponse()) + ->status($responseItem['status']) + ->ticketId($responseItem['id']) + ); + } + }); + + return $this; + } + + private function sendNotificationsChunk(array $chunk) + { + $response = $this->http->post(self::SEND_NOTIFICATION_ENDPOINT, $chunk); + + if (! $response->successful()) { + throw new ExpoNotificationsException($response->toPsrResponse()); + } + + $this->handleSendNotificationsResponse($response); + } } diff --git a/src/ExpoNotificationsServiceProvider.php b/src/ExpoNotificationsServiceProvider.php index 8549d87..6dd8d0c 100644 --- a/src/ExpoNotificationsServiceProvider.php +++ b/src/ExpoNotificationsServiceProvider.php @@ -8,6 +8,7 @@ use Illuminate\Support\ServiceProvider; use YieldStudio\LaravelExpoNotifier\Commands\CheckTickets; use YieldStudio\LaravelExpoNotifier\Commands\SendPendingNotifications; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoNotificationsServiceInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoPendingNotificationStorageInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoTicketStorageInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoTokenStorageInterface; @@ -22,7 +23,7 @@ public function register(): void $this->app->bind(ExpoTicketStorageInterface::class, config('expo-notifications.drivers.ticket')); $this->app->bind(ExpoPendingNotificationStorageInterface::class, config('expo-notifications.drivers.notification')); - $this->app->bind(ExpoNotificationsService::class, function ($app) { + $this->app->bind(ExpoNotificationsServiceInterface::class, function ($app) { $apiUrl = config('expo-notifications.service.api_url'); $host = config('expo-notifications.service.host'); diff --git a/src/FakeExpoNotificationsService.php b/src/FakeExpoNotificationsService.php new file mode 100644 index 0000000..00e5d24 --- /dev/null +++ b/src/FakeExpoNotificationsService.php @@ -0,0 +1,184 @@ +tickets = collect(); + } + + public function notify(ExpoMessage|Collection|array $expoMessages): Collection + { + /** @var Collection $expoMessages */ + $this->expoMessages = $expoMessages instanceof Collection ? $expoMessages : collect(Arr::wrap($expoMessages)); + + return $this->storeNotificationsToSendInTheNextBatch() + ->prepareNotificationsToSendNow() + ->sendNotifications(); + } + + public function receipts(array|Collection $tokenIds): Collection + { + // TODO: Implement receipts() method. + } + + public function getNotificationChunks(): Collection + { + return $this->notificationChunks ?? collect(); + } + + private function prepareNotificationsToSendNow(): FakeExpoNotificationsService + { + $this->notificationsToSend = $this->expoMessages + ->reject(fn (ExpoMessage $message) => $message->shouldBatch) + ->map(fn (ExpoMessage $message) => $message->toExpoData()) + ->values(); + + // Splits into multiples chunks of max limitation + $this->notificationChunks = $this->notificationsToSend->chunk(self::PUSH_NOTIFICATIONS_PER_REQUEST_LIMIT); + + return $this; + } + + private function storeNotificationsToSendInTheNextBatch(): FakeExpoNotificationsService + { + $this->expoMessages + ->filter(fn (ExpoMessage $message) => $message->shouldBatch) + ->each(fn (ExpoMessage $message) => $this->notificationStorage->store($message)); + + return $this; + } + + private function sendNotifications(): Collection + { + if ($this->notificationsToSend->isEmpty()) { + return collect(); + } + + $this->notificationChunks + ->each( + fn ($chunk, $index) => $this->sendNotificationsChunk($chunk->toArray(), $index) + ); + + $this->checkAndStoreTickets($this->notificationsToSend->pluck('to')->flatten()); + + return $this->tickets; + } + + private function sendNotificationsChunk(array $chunk, int $chunkId): void + { + $data = []; + foreach ($chunk as $notification) { + $data[] = [ + 'id' => Str::orderedUuid()->toString(), + 'status' => ExpoResponseStatus::OK->value, + '__notification' => $notification, + ]; + } + + $response = Http::fake([ + 'api-push/'.$chunkId => Http::response([ + 'data' => $data, + ]), + ])->get('/api-push/'.$chunkId); + + if (! $response->successful()) { + throw new ExpoNotificationsException($response->toPsrResponse()); + } + + $this->handleSendNotificationsResponse($response); + } + + private function handleSendNotificationsResponse(Response $response): void + { + $data = json_decode($response->body(), true, 512, JSON_THROW_ON_ERROR); + if (! empty($data['errors'])) { + throw new ExpoNotificationsException($response->toPsrResponse()); + } + + $this->setTicketsFromData($data); + } + + private function setTicketsFromData(array $data): FakeExpoNotificationsService + { + collect($data['data']) + ->each(function ($responseItem) { + if ($responseItem['status'] === ExpoResponseStatus::ERROR->value) { + $this->tickets->push( + (new PushTicketResponse()) + ->status($responseItem['status']) + ->message($responseItem['message']) + ->details($responseItem['details']) + ); + } else { + $this->tickets->push( + (new PushTicketResponse()) + ->status($responseItem['status']) + ->ticketId($responseItem['id']) + ); + } + }); + + return $this; + } + + /** + * @param Collection $tokens + */ + private function checkAndStoreTickets(Collection $tokens): void + { + $this->tickets + ->intersectByKeys($tokens) + ->each(function (PushTicketResponse $ticket, $index) use ($tokens) { + if ($ticket->status === ExpoResponseStatus::ERROR->value) { + if ( + is_array($ticket->details) && + array_key_exists('error', $ticket->details) && + $ticket->details['error'] === ExpoResponseStatus::DEVICE_NOT_REGISTERED->value + ) { + event(new InvalidExpoToken($tokens->get($index))); + } + } else { + $this->ticketStorage->store($ticket->ticketId, $tokens->get($index)); + } + }); + } +} diff --git a/src/Jobs/CheckTickets.php b/src/Jobs/CheckTickets.php index 445d8fe..b675e99 100644 --- a/src/Jobs/CheckTickets.php +++ b/src/Jobs/CheckTickets.php @@ -8,11 +8,11 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoNotificationsServiceInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoTicketStorageInterface; use YieldStudio\LaravelExpoNotifier\Dto\ExpoTicket; use YieldStudio\LaravelExpoNotifier\Enums\ExpoResponseStatus; use YieldStudio\LaravelExpoNotifier\Events\InvalidExpoToken; -use YieldStudio\LaravelExpoNotifier\ExpoNotificationsService; class CheckTickets { @@ -21,7 +21,7 @@ class CheckTickets use SerializesModels; public function handle( - ExpoNotificationsService $expoNotificationsService, + ExpoNotificationsServiceInterface $expoNotificationsService, ExpoTicketStorageInterface $ticketStorage ): void { while ($ticketStorage->count() > 0) { diff --git a/src/Jobs/SendPendingNotifications.php b/src/Jobs/SendPendingNotifications.php index ebcad36..300f1a8 100644 --- a/src/Jobs/SendPendingNotifications.php +++ b/src/Jobs/SendPendingNotifications.php @@ -7,9 +7,9 @@ use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\SerializesModels; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoNotificationsServiceInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoPendingNotificationStorageInterface; use YieldStudio\LaravelExpoNotifier\Dto\ExpoNotification; -use YieldStudio\LaravelExpoNotifier\ExpoNotificationsService; class SendPendingNotifications { @@ -18,7 +18,7 @@ class SendPendingNotifications use SerializesModels; public function handle( - ExpoNotificationsService $expoNotificationsService, + ExpoNotificationsServiceInterface $expoNotificationsService, ExpoPendingNotificationStorageInterface $expoNotification, ): void { $sent = collect(); diff --git a/tests/Feature/ExpoNotificationServiceTest.php b/tests/Feature/ExpoNotificationServiceTest.php new file mode 100644 index 0000000..65e56ef --- /dev/null +++ b/tests/Feature/ExpoNotificationServiceTest.php @@ -0,0 +1,35 @@ +messages = collect(); + + for ($i = 0; $i < 120; $i++) { + $this->messages->push( + (new ExpoMessage()) + ->to([Str::orderedUuid()->toString()]) + ->title("A beautiful title #$i") + ->body('This is a content') + ->channelId('default') + ); + } + $this->notificationService = app(ExpoNotificationsServiceInterface::class); +}); + +it("creates 2 chunks if we're sending 20 notifications above limit", function () { + $this->notificationService->notify($this->messages); + $count = $this->notificationService->getNotificationChunks()->count(); + + expect($count)->toBe(2) + ->and(app(ExpoTicketStorageInterface::class)->count()) + ->toBe($this->messages->count()); +}); diff --git a/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php b/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php new file mode 100644 index 0000000..e864d89 --- /dev/null +++ b/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php @@ -0,0 +1,46 @@ + json_encode([ + 'foo' => fake()->slug, + ], JSON_THROW_ON_ERROR), + ]); + } + + $this->notifications = ExpoNotification::all(); + $this->notificationStorage = app(config('expo-notifications.drivers.notification')); +}); + +it('retrieves notifications from storage', function () { + $retrievedNotifications = $this->notificationStorage->retrieve(); + + expect($retrievedNotifications) + ->toBeInstanceOf(Collection::class) + ->and($retrievedNotifications->first()->id) + ->toBe($this->notifications->first()->id) + ->and($retrievedNotifications->first()->message->foo) + ->toBe(json_decode($this->notifications->first()->data, true, 512, JSON_THROW_ON_ERROR)['foo']) + ->and($retrievedNotifications->get(2)->id) + ->toBe($this->notifications->get(2)->id) + ->and($retrievedNotifications->get(2)->message->foo) + ->toBe(json_decode($this->notifications->get(2)->data, true, 512, JSON_THROW_ON_ERROR)['foo']); +}); + +it('retrieves a max of 100 notifications', function () { + $retrievedNotifications = $this->notificationStorage->retrieve(); + + expect($retrievedNotifications) + ->toBeInstanceOf(Collection::class) + ->and($retrievedNotifications->last()->id) + ->toBe(100); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 96660e3..97846e0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,12 +5,60 @@ namespace YieldStudio\LaravelExpoNotifier\Tests; use Orchestra\Testbench\TestCase as Orchestra; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoNotificationsServiceInterface; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoPendingNotificationStorageInterface; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoTicketStorageInterface; +use YieldStudio\LaravelExpoNotifier\Contracts\ExpoTokenStorageInterface; use YieldStudio\LaravelExpoNotifier\ExpoNotificationsServiceProvider; +use YieldStudio\LaravelExpoNotifier\FakeExpoNotificationsService; +use YieldStudio\LaravelExpoNotifier\Storage\ExpoPendingNotificationStorageMysql; +use YieldStudio\LaravelExpoNotifier\Storage\ExpoTicketStorageMysql; +use YieldStudio\LaravelExpoNotifier\Storage\ExpoTokenStorageMysql; -abstract class TestCase extends Orchestra +class TestCase extends Orchestra { protected function getPackageProviders($app): array { return [ExpoNotificationsServiceProvider::class]; } + + protected function setUp(): void + { + parent::setUp(); + + $this->app->bind(ExpoTokenStorageInterface::class, config('expo-notifications.drivers.token')); + $this->app->bind(ExpoTicketStorageInterface::class, config('expo-notifications.drivers.ticket')); + $this->app->bind(ExpoPendingNotificationStorageInterface::class, config('expo-notifications.drivers.notification')); + + $this->app->bind(ExpoNotificationsServiceInterface::class, function ($app) { + return new FakeExpoNotificationsService( + 'http://localhost', // won't be used, just here to respect the contract + 'localhost', // won't be used, just here to respect the contract + $app->make(ExpoPendingNotificationStorageInterface::class), + $app->make(ExpoTicketStorageInterface::class) + ); + }); + } + + protected function getEnvironmentSetUp($app): void + { + // Setup default database to use sqlite :memory: + config()->set('database.default', 'testbench'); + config()->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + + // Setup queue database connections. + config()->set('queue.batching.database', 'testbench'); + config()->set('queue.failed.database', 'testbench'); + + // Setup Expo configuration + config()->set('expo-notifications.drivers', [ + 'token' => ExpoTokenStorageMysql::class, + 'ticket' => ExpoTicketStorageMysql::class, + 'notification' => ExpoPendingNotificationStorageMysql::class, + ]); + } } From 06d8f574b8b753d4fd3404f68bc49447c3c3d91b Mon Sep 17 00:00:00 2001 From: Joe FRANCOIS Date: Thu, 15 Feb 2024 15:56:24 +0100 Subject: [PATCH 2/5] test: adds fake() helper method for Laravel 9 `fake()` function does not exists on Laravel 9 --- .../Feature/Storage/ExpoPendingNotificationStorageTest.php | 2 +- tests/TestCase.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php b/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php index e864d89..bb788d0 100644 --- a/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php +++ b/tests/Feature/Storage/ExpoPendingNotificationStorageTest.php @@ -12,7 +12,7 @@ for ($i = 0; $i < 120; $i++) { ExpoNotification::create([ 'data' => json_encode([ - 'foo' => fake()->slug, + 'foo' => $this->fake()->slug, ], JSON_THROW_ON_ERROR), ]); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 97846e0..14ff86f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,6 +4,8 @@ namespace YieldStudio\LaravelExpoNotifier\Tests; +use Faker\Factory; +use Faker\Generator; use Orchestra\Testbench\TestCase as Orchestra; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoNotificationsServiceInterface; use YieldStudio\LaravelExpoNotifier\Contracts\ExpoPendingNotificationStorageInterface; @@ -61,4 +63,9 @@ protected function getEnvironmentSetUp($app): void 'notification' => ExpoPendingNotificationStorageMysql::class, ]); } + + protected function fake(): Generator + { + return Factory::create(); + } } From 1e967f2acdc979701f9010e4912c9fb0c266be7d Mon Sep 17 00:00:00 2001 From: Joe FRANCOIS Date: Thu, 15 Feb 2024 16:35:42 +0100 Subject: [PATCH 3/5] chore: sets the minimum Carbon version to 2.62.1 to avoid lastErrors() issue --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 982d728..35a30bc 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "require": { "php": "^8.1", "illuminate/database": "^9|^10", - "illuminate/support": "^9|^10" + "illuminate/support": "^9|^10", + "nesbot/carbon": ">=2.62.1" }, "require-dev": { "ciareis/bypass": "^1.0", From bf9760b2ed2cc46b6889a0b14bd5d18820446b78 Mon Sep 17 00:00:00 2001 From: Joe FRANCOIS Date: Fri, 16 Feb 2024 12:29:58 +0100 Subject: [PATCH 4/5] refactor: removes obsolete line --- src/ExpoNotificationsService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ExpoNotificationsService.php b/src/ExpoNotificationsService.php index fa3ae41..f964759 100644 --- a/src/ExpoNotificationsService.php +++ b/src/ExpoNotificationsService.php @@ -150,7 +150,6 @@ private function prepareNotificationsToSendNow(): ExpoNotificationsService // Splits into multiples chunks of max limitation $this->notificationChunks = $this->notificationsToSend->chunk(self::PUSH_NOTIFICATIONS_PER_REQUEST_LIMIT); - $this->checkAndStoreTickets($this->notificationsToSend->pluck('to')->flatten()); return $this; } From ec45977cba8f68cdf5c22c7a1411312080639b1e Mon Sep 17 00:00:00 2001 From: Joe FRANCOIS Date: Fri, 16 Feb 2024 12:31:05 +0100 Subject: [PATCH 5/5] chore: applies lint rules --- src/ExpoNotificationsService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ExpoNotificationsService.php b/src/ExpoNotificationsService.php index f964759..4bc079d 100644 --- a/src/ExpoNotificationsService.php +++ b/src/ExpoNotificationsService.php @@ -150,7 +150,6 @@ private function prepareNotificationsToSendNow(): ExpoNotificationsService // Splits into multiples chunks of max limitation $this->notificationChunks = $this->notificationsToSend->chunk(self::PUSH_NOTIFICATIONS_PER_REQUEST_LIMIT); - return $this; }