Expo channel for pushing notifications to your React Native apps.
- Installation
- Additional Security
- Usage
- Expo Message Request Format
- Testing
- Changelog
- Contributing
- Security
- Credits
- License
You can install the package via Composer:
composer require laravel-notification-channels/expo
You can require any push notifications to be sent with an additional Access Token before Expo delivers them to your users.
If you want to make use of this additional security layer, add the following to your config/services.php
file:
'expo' => [
'access_token' => env('EXPO_ACCESS_TOKEN'),
],
You can now use the expo
channel in the via()
method of your Notification
s.
First things first, you need to have a Notification that needs to be delivered to someone. Check out the Laravel documentation for more information on generating notifications.
use NotificationChannels\Expo\ExpoMessage;
class SuspiciousActivityDetected extends Notification
{
public function toExpo($notifiable): ExpoMessage
{
return ExpoMessage::create('Suspicious Activity')
->body('Someone tried logging in to your account!')
->data($notifiable->only('email', 'id'))
->expiresAt(now()->addHour())
->priority('high')
->playSound();
}
public function via($notifiable): array
{
return ['expo'];
}
}
Note
Detailed explanation regarding the Expo Message Request Format can be found here.
You can also apply conditionals to ExpoMessage
without breaking the method chain:
use NotificationChannels\Expo\ExpoMessage;
public function toExpo($notifiable): ExpoMessage
{
return ExpoMessage::create('Suspicious Activity')
->body('Someone tried logging in to your account!')
->when($notifiable->wantsSound(), fn ($msg) => $msg->playSound())
->unless($notifiable->isVip(), fn ($msg) => $msg->normal(), fn ($msg) => $msg->high());
}
Next, you will have to set a routeNotificationForExpo()
method in your Notifiable
model.
The method must return either an instance of ExpoPushToken
or null
. An example:
use NotificationChannels\Expo\ExpoPushToken;
class User extends Authenticatable
{
use Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'expo_token' => ExpoPushToken::class
];
}
public function routeNotificationForExpo(): ?ExpoPushToken
{
return $this->expo_token;
}
}
Important
No notifications will be sent in case of null
.
Note
More info regarding the model cast can be found here.
The method must return an array<int, ExpoPushToken>
or Collection<int, ExpoPushToken>
,
the specific implementation depends on your use case. An example:
use Illuminate\Database\Eloquent\Collection;
class User extends Authenticatable
{
use Notifiable;
/**
* @return Collection<int, ExpoPushToken>
*/
public function routeNotificationForExpo(): Collection
{
return $this->devices->pluck('expo_token');
}
}
Important
No notifications will be sent in case of an empty Collection
.
Once everything is in place, you can simply send a notification by calling:
$user->notify(new SuspiciousActivityDetected());
You ought to have an HTTP endpoint that associates a given ExpoPushToken
with an authenticated User
so that you can deliver push notifications. For this reason, we're also providing a custom validation ExpoPushTokenRule
class which you can use to protect your endpoints. An example:
use NotificationChannels\Expo\ExpoPushToken;
class StoreDeviceRequest extends FormRequest
{
public function rules(): array
{
return [
'device_id' => ['required', 'string', 'min:2', 'max:255'],
'token' => ['required', ExpoPushToken::rule()],
];
}
}
The ExpoChannel
expects you to return an instance of ExpoPushToken
from your Notifiable
s. You can easily achieve this by applying the ExpoPushToken
as a custom model cast. An example:
use NotificationChannels\Expo\ExpoPushToken;
class User extends Authenticatable
{
use Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'expo_token' => ExpoPushToken::class
];
}
}
This custom value object guarantees the integrity of the push token. You should make sure that only valid tokens are saved.
Unfortunately, Laravel does not provide an OOB solution for handling failed deliveries. However, there is a NotificationFailed
event which Laravel does provide so you can hook into failed delivery attempts. This is particularly useful when an old token is no longer valid and the service starts responding with DeviceNotRegistered
errors.
You can register an event listener that listens to this event and handles the appropriate errors. An example:
use Illuminate\Notifications\Events\NotificationFailed;
class HandleFailedExpoNotifications
{
public function handle(NotificationFailed $event)
{
if ($event->channel !== 'expo') return;
/** @var ExpoError $error */
$error = $event->data;
// Remove old token
if ($error->type->isDeviceNotRegistered()) {
$event->notifiable->update(['expo_token' => null]);
} else {
// do something else like logging...
}
}
}
The NotificationFailed::$data
property will contain an instance of ExpoError
which has the following properties:
namespace NotificationChannels\Expo;
final readonly class ExpoError
{
private function __construct(
public ExpoErrorType $type,
public ExpoPushToken $token,
public string $message,
) {}
}
The ExpoMessage
class contains the following methods for defining the message payload. All of these methods correspond to the available payload defined in the Expo Push documentation.
- Badge (iOS)
- Body
- Category ID
- Channel ID (Android)
- JSON data
- Expiration
- Mutable content (iOS)
- Notification sound (iOS)
- Priority
- Subtitle (iOS)
- Title
- TTL (Time to live)
Sets the number to display in the badge on the app icon.
badge(int $value)
Note
The value must be greater than or equal to 0.
Sets the message body to display in the notification.
body(string $value)
text(string $value)
Note
The value must not be empty.
Sets the ID of the notification category that this notification is associated with.
categoryId(string $value)
Note
The value must not be empty.
Sets the ID of the Notification Channel through which to display this notification.
channelId(string $value)
Note
The value must not be empty.
Sets the JSON data for the message.
data(Arrayable|Jsonable|JsonSerializable|array $value)
Warning
We're compressing JSON payloads that exceed 1 KiB using Gzip (if ext-zlib
is available). While you could technically send more than 4 KiB of data, this is not recommended.
Sets the expiration time of the message. Same effect as TTL.
expiresAt(DateTimeInterface|int $value)
Warning
TTL
takes precedence if both are set.
Note
The value must be in the future.
Sets whether the notification can be intercepted by the client app.
mutableContent(bool $value = true)
Play the default notification sound when the recipient receives the notification.
playSound()
Warning
Custom sounds are not supported.
Sets the delivery priority of the message.
priority(string $value)
default()
normal()
high()
Note
The value must be default
, normal
or high
.
Sets the subtitle to display in the notification below the title.
subtitle(string $value)
Note
The value must not be empty.
Set the title to display in the notification.
title(string $value)
Note
The value must not be empty.
Set the number of seconds for which the message may be kept around for redelivery.
ttl(int $value)
expiresIn(int $value)
Warning
Takes precedence over expiration
if both are set.
Note
The value must be greater than 0.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.