├── .editorconfig ├── .github └── workflows │ └── run-tests.yml ├── .styleci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── ApnAdapter.php ├── ApnChannel.php ├── ApnMessage.php ├── ApnMessageInterruptionLevel.php ├── ApnMessagePriority.php ├── ApnServiceProvider.php ├── ApnVoipChannel.php ├── ApnVoipMessage.php └── ClientFactory.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | test: 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest] 14 | php: [8.2, 8.3, 8.4] 15 | laravel: ['11.*', '12.*'] 16 | stability: [prefer-lowest, prefer-stable] 17 | exclude: 18 | - laravel: 11.* 19 | php: 8.1 20 | 21 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 22 | 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v1 26 | 27 | - name: Setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update 35 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 36 | 37 | - name: Execute tests 38 | run: vendor/bin/phpunit 39 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes will be documented in this file 4 | 5 | ## 2.0.0 - 2020-03-04 6 | - Simplify configuration for the production environment (#80) 7 | - Add support for Laravel 7.0 (#82) 8 | 9 | ## 1.3.0 - 2020-02-24 10 | - `NotificationFailed` event dispatched when a notification to a token is unsuccessful (#79) 11 | 12 | ## 1.2.0 - 2020-02-21 13 | - You can now provide a configuration per-message if you want to use different clients (#52) 14 | - You can now set a message expiry thanks to improved internal abstractions (#78) 15 | 16 | ## 1.1.1 - 2020-02-20 17 | - Return the response value from the `send` method so it is available in the `NotificationSent` event (#76) 18 | - Add `pushType` to support custom push types (#69) 19 | 20 | ## 1.1.0 - 2020-02-18 21 | - Bump the minimum Pushok version (5ab28d108200b378ae477624948fb22f97d41356) 22 | 23 | ## 1.0.1 - 2020-02-18 24 | - Support passing the token content in directly, instead of having to provide a path to the token file (fbc2ab66b199be383a1938fee040ec4f5cea4a08) 25 | - Support the ability to provide additional configuration to the underlying `Token` class (fbc2ab66b199be383a1938fee040ec4f5cea4a08) 26 | 27 | ## 1.0.0 - 201X-02-18 28 | - Replace Zend dependency with newer library called Pushok that supports .p8 tokens for authentication (#67) 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Fruitcake 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Laravel APN (Apple Push) Notification Channel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/laravel-notification-channels/apn.svg?style=flat-square)](https://packagist.org/packages/laravel-notification-channels/apn) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | [![Build Status](https://img.shields.io/travis/laravel-notification-channels/apn/master.svg?style=flat-square)](https://travis-ci.org/laravel-notification-channels/apn) 6 | [![StyleCI](https://styleci.io/repos/66449499/shield)](https://github.styleci.io/repos/66449499) 7 | [![Quality Score](https://img.shields.io/scrutinizer/g/laravel-notification-channels/apn.svg?style=flat-square)](https://scrutinizer-ci.com/g/laravel-notification-channels/apn) 8 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/laravel-notification-channels/apn/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/laravel-notification-channels/apn/?branch=master) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/laravel-notification-channels/apn.svg?style=flat-square)](https://packagist.org/packages/laravel-notification-channels/apn) 10 | 11 | This package makes it easy to send notifications using Apple Push (APN) with Laravel. 12 | 13 | ## Contents 14 | 15 | - [Installation](#installation) 16 | - [Usage](#usage) 17 | - [Changelog](#changelog) 18 | - [Testing](#testing) 19 | - [Security](#security) 20 | - [Contributing](#contributing) 21 | - [Credits](#credits) 22 | - [License](#license) 23 | 24 | 25 | ## Installation 26 | 27 | Install this package with Composer: 28 | 29 | composer require laravel-notification-channels/apn 30 | 31 | ### Setting up the APN service 32 | 33 | Before using the APN Service, [enable Push Notifications in your app](https://help.apple.com/xcode/mac/current/#/devdfd3d04a1). Then [create a APNS key under Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/authkeys/list) to generate a Key ID and .p8 file. 34 | 35 | Collect your Key ID, as well as your Team ID (displayed at the top right of the Apple Developer page) and app bundle ID and configure as necessary in `config/broadcasting.php`. 36 | 37 | ## JWT Token Authentication 38 | 39 | ```php 40 | 'connections' => [ 41 | 'apn' => [ 42 | 'key_id' => env('APN_KEY_ID'), 43 | 'team_id' => env('APN_TEAM_ID'), 44 | 'app_bundle_id' => env('APN_BUNDLE_ID'), 45 | // Enable either `private_key_path` or `private_key_content` depending on your environment 46 | // 'private_key_path' => env('APN_PRIVATE_KEY'), 47 | 'private_key_content' => env('APN_PRIVATE_KEY'), 48 | 'private_key_secret' => env('APN_PRIVATE_SECRET'), 49 | 'production' => env('APN_PRODUCTION', true), 50 | ], 51 | ], 52 | ``` 53 | 54 | See the [Establishing a token-based connection to APNs](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns) which will guide you how to obtain the values of the necessary parameters. 55 | 56 | ## Using Certificate (.pem) Authentication 57 | 58 | ```php 59 | 'connections' => [ 60 | 'apn' => [ 61 | 'app_bundle_id' => env('APN_BUNDLE_ID'), 62 | 'certificate_path' => env('APN_CERTIFICATE_PATH'), 63 | 'certificate_secret' => env('APN_CERTIFICATE_SECRET'), 64 | 'production' => env('APN_PRODUCTION', true), 65 | ], 66 | ], 67 | ``` 68 | If you are connecting with certificate based APNs, `key_id` and `team_id` are not needed. You can refer to [Send a Push Notification Using a Certificate](https://developer.apple.com/documentation/usernotifications/sending_push_notifications_using_command-line_tools#3694578) 69 | 70 | See the [Establishing a certificate-based connection to APNs](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns) which will guide you how to obtain the values of the necessary parameters. 71 | 72 | See the [`pushok` docs](https://github.com/edamov/pushok) for more information about what arguments can be supplied to the client. 73 | 74 | ## Usage 75 | 76 | You can now send messages to APN by creating a ApnMessage: 77 | 78 | ```php 79 | use NotificationChannels\Apn\ApnChannel; 80 | use NotificationChannels\Apn\ApnMessage; 81 | use Illuminate\Notifications\Notification; 82 | 83 | class AccountApproved extends Notification 84 | { 85 | public function via($notifiable) 86 | { 87 | return [ApnChannel::class]; 88 | } 89 | 90 | public function toApn($notifiable) 91 | { 92 | return ApnMessage::create() 93 | ->badge(1) 94 | ->title('Account approved') 95 | ->body("Your {$notifiable->service} account was approved!"); 96 | } 97 | } 98 | ``` 99 | 100 | To see more of the methods available to you when creating a message please see the [`ApnMessage` source](https://github.com/laravel-notification-channels/apn/blob/master/src/ApnMessage.php). 101 | 102 | In your `notifiable` model, make sure to include a `routeNotificationForApn()` method, which return one or an array of tokens. 103 | 104 | ```php 105 | public function routeNotificationForApn() 106 | { 107 | return $this->apn_token; 108 | } 109 | ``` 110 | 111 | ### Per-message configuration 112 | 113 | If you need to provide a custom configuration for a message you can provide an instance of a [Pushok](https://github.com/edamov/pushok) client and it will be used instead of the default one. 114 | 115 | ```php 116 | $customClient = new Pushok\Client(Pushok\AuthProvider\Token::create($options)); 117 | 118 | return ApnMessage::create() 119 | ->title('Account approved') 120 | ->body("Your {$notifiable->service} account was approved!") 121 | ->via($customClient) 122 | ``` 123 | 124 | ### VoIP push notifications 125 | 126 | Sending VoIP push notifications is very similar. You just need to use the `ApnVoipChannel` channel with `ApnVoipMessage` (which has the same API as a regular `ApnMessage`). 127 | 128 | ```php 129 | use NotificationChannels\Apn\ApnVoipChannel; 130 | use NotificationChannels\Apn\ApnVoipMessage; 131 | use Illuminate\Notifications\Notification; 132 | 133 | class AccountApproved extends Notification 134 | { 135 | public function via($notifiable) 136 | { 137 | return [ApnVoipChannel::class]; 138 | } 139 | 140 | public function toApnVoip($notifiable) 141 | { 142 | return ApnVoipMessage::create() 143 | ->badge(1); 144 | } 145 | } 146 | ``` 147 | 148 | In your `notifiable` model, make sure to include a `routeNotificationForApnVoip()` method, which return one or an array of tokens. 149 | 150 | ```php 151 | public function routeNotificationForApnVoip() 152 | { 153 | return $this->apn_voip_token; 154 | } 155 | ``` 156 | 157 | ## Changelog 158 | 159 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 160 | 161 | ## Testing 162 | 163 | ``` bash 164 | $ composer test 165 | ``` 166 | 167 | ## Security 168 | 169 | If you discover any security related issues, please email info@fruitcake.nl instead of using the issue tracker. 170 | 171 | ## Contributing 172 | 173 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 174 | 175 | ## Credits 176 | 177 | - [Fruitcake](https://github.com/fruitcake) 178 | - [All Contributors](../../contributors) 179 | 180 | ## License 181 | 182 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 183 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-notification-channels/apn", 3 | "description": "Apple APN Push Notification Channel", 4 | "homepage": "https://github.com/laravel-notification-channels/apn", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Fruitcake", 9 | "email": "info@fruitcake.nl", 10 | "homepage": "https://fruitcake.nl" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.2", 15 | "edamov/pushok": "^0.18", 16 | "illuminate/cache": "^11.0|^12.0", 17 | "illuminate/config": "^11.0|^12.0", 18 | "illuminate/events": "^11.0|^12.0", 19 | "illuminate/notifications": "^11.0|^12.0", 20 | "illuminate/support": "^11.0|^12.0", 21 | "nesbot/carbon": "^2.66|^3.0" 22 | }, 23 | "require-dev": { 24 | "mockery/mockery": "^1.5.1", 25 | "phpunit/phpunit": "^11.0", 26 | "squizlabs/php_codesniffer": "^3.5" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "NotificationChannels\\Apn\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "NotificationChannels\\Apn\\Tests\\": "tests" 36 | } 37 | }, 38 | "scripts": { 39 | "test": "vendor/bin/phpunit" 40 | }, 41 | "config": { 42 | "sort-packages": true 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "NotificationChannels\\Apn\\ApnServiceProvider" 48 | ] 49 | } 50 | }, 51 | "minimum-stability": "dev", 52 | "prefer-stable": true 53 | } 54 | -------------------------------------------------------------------------------- /src/ApnAdapter.php: -------------------------------------------------------------------------------- 1 | pushType !== ApnMessage::PUSH_TYPE_BACKGROUND) { 19 | $alert = Alert::create(); 20 | 21 | if ($title = $message->title) { 22 | $alert->setTitle($title); 23 | } 24 | 25 | if ($subtitle = $message->subtitle) { 26 | $alert->setSubtitle($subtitle); 27 | } 28 | 29 | if ($body = $message->body) { 30 | $alert->setBody($body); 31 | } 32 | 33 | if ($titleLocArgs = $message->titleLocArgs) { 34 | $alert->setTitleLocArgs($titleLocArgs); 35 | } 36 | 37 | if ($titleLocKey = $message->titleLocKey) { 38 | $alert->setTitleLocKey($titleLocKey); 39 | } 40 | 41 | if ($actionLocKey = $message->actionLocKey) { 42 | $alert->setActionLocKey($actionLocKey); 43 | } 44 | 45 | if ($locArgs = $message->locArgs) { 46 | $alert->setLocArgs($locArgs); 47 | } 48 | 49 | if ($locKey = $message->locKey) { 50 | $alert->setLocKey($locKey); 51 | } 52 | } 53 | 54 | $payload = Payload::create()->setAlert($message->customAlert ?: $alert); 55 | 56 | if ($contentAvailable = $message->contentAvailable) { 57 | $payload->setContentAvailability((bool) $message->contentAvailable); 58 | } 59 | 60 | if ($mutableContent = $message->mutableContent) { 61 | $payload->setMutableContent((bool) $message->mutableContent); 62 | } 63 | 64 | if (is_int($badge = $message->badge)) { 65 | $payload->setBadge($badge); 66 | } 67 | 68 | if ($sound = $message->sound) { 69 | $payload->setSound($sound); 70 | } 71 | 72 | if ($interruptionLevel = $message->interruptionLevel) { 73 | $payload->setInterruptionLevel($interruptionLevel); 74 | } 75 | 76 | if ($category = $message->category) { 77 | $payload->setCategory($category); 78 | } 79 | 80 | if ($threadId = $message->threadId) { 81 | $payload->setThreadId($threadId); 82 | } 83 | 84 | if ($urlArgs = $message->urlArgs) { 85 | $payload->setUrlArgs($message->urlArgs); 86 | } 87 | 88 | foreach ($message->custom as $key => $value) { 89 | $payload->setCustomValue($key, $value); 90 | } 91 | 92 | if ($pushType = $message->pushType) { 93 | $payload->setPushType($pushType); 94 | } 95 | 96 | $notification = new Notification($payload, $token); 97 | 98 | if ($expiresAt = $message->expiresAt) { 99 | $notification->setExpirationAt($expiresAt); 100 | } 101 | 102 | if ($collapseId = $message->collapseId) { 103 | $notification->setCollapseId($collapseId); 104 | } 105 | 106 | if ($apnsId = $message->apnsId) { 107 | $notification->setId($apnsId); 108 | } 109 | 110 | if ($message->priority === ApnMessagePriority::Low) { 111 | $notification->setLowPriority(); 112 | } 113 | 114 | if ($message->priority === ApnMessagePriority::High) { 115 | $notification->setHighPriority(); 116 | } 117 | 118 | return $notification; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/ApnChannel.php: -------------------------------------------------------------------------------- 1 | routeNotificationFor('apn', $notification); 27 | 28 | if (empty($tokens)) { 29 | return null; 30 | } 31 | 32 | $message = $notification->toApn($notifiable); 33 | 34 | $client = $message->client ?? $this->factory->instance(); 35 | 36 | $responses = $this->sendNotifications($client, $message, $tokens); 37 | 38 | $this->dispatchEvents($notifiable, $notification, $responses); 39 | 40 | return $responses; 41 | } 42 | 43 | /** 44 | * Send the message to the given tokens through the given client. 45 | */ 46 | protected function sendNotifications(Client $client, ApnMessage $message, array $tokens): array 47 | { 48 | foreach ($tokens as $token) { 49 | $client->addNotification((new ApnAdapter)->adapt($message, $token)); 50 | } 51 | 52 | return $client->push(); 53 | } 54 | 55 | /** 56 | * Dispatch failed events for notifications that weren't delivered. 57 | */ 58 | protected function dispatchEvents(mixed $notifiable, Notification $notification, array $responses): void 59 | { 60 | foreach ($responses as $response) { 61 | if ($response->getStatusCode() === Response::APNS_SUCCESS) { 62 | continue; 63 | } 64 | 65 | $event = new NotificationFailed($notifiable, $notification, static::class, [ 66 | 'id' => $response->getApnsId(), 67 | 'token' => $response->getDeviceToken(), 68 | 'error' => $response->getErrorReason(), 69 | ]); 70 | 71 | $this->events->dispatch($event); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/ApnMessage.php: -------------------------------------------------------------------------------- 1 | title = $title; 147 | $this->body = $body; 148 | $this->custom = $custom; 149 | $this->badge = $badge; 150 | } 151 | 152 | /** 153 | * Create a new message instance. 154 | */ 155 | public static function create(?string $title = null, ?string $body = null, array $custom = [], ?int $badge = null): static 156 | { 157 | return new static($title, $body, $custom, $badge); 158 | } 159 | 160 | /** 161 | * Set the alert title of the notification. 162 | */ 163 | public function title(?string $title): self 164 | { 165 | $this->title = $title; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Set the alert subtitle of the notification. 172 | */ 173 | public function subtitle(?string $subtitle): self 174 | { 175 | $this->subtitle = $subtitle; 176 | 177 | return $this; 178 | } 179 | 180 | /** 181 | * Set the alert message of the notification. 182 | */ 183 | public function body(?string $body): self 184 | { 185 | $this->body = $body; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Set the badge of the notification. 192 | */ 193 | public function badge(?int $badge): self 194 | { 195 | $this->badge = $badge; 196 | 197 | return $this; 198 | } 199 | 200 | /** 201 | * Set the sound of the notification. 202 | */ 203 | public function sound(null|string|Sound $sound = 'default'): self 204 | { 205 | $this->sound = $sound; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Set the interruptionLevel of the notification. 212 | */ 213 | public function interruptionLevel(string|ApnMessageInterruptionLevel|null $interruptionLevel = 'active'): self 214 | { 215 | $this->interruptionLevel = $interruptionLevel instanceof ApnMessageInterruptionLevel 216 | ? $interruptionLevel->value 217 | : $interruptionLevel; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * Set category of the notification. 224 | * */ 225 | public function category(?string $category): self 226 | { 227 | $this->category = $category; 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * Set thread ID of the notification. 234 | * */ 235 | public function threadId(?string $threadId): self 236 | { 237 | $this->threadId = $threadId; 238 | 239 | return $this; 240 | } 241 | 242 | /** 243 | * Set content available value of the notification. 244 | */ 245 | public function contentAvailable(?int $value = 1): self 246 | { 247 | $this->contentAvailable = $value; 248 | 249 | return $this; 250 | } 251 | 252 | /** 253 | * Set the push type of the notification. 254 | */ 255 | public function pushType(?string $pushType): self 256 | { 257 | $this->pushType = $pushType; 258 | 259 | return $this; 260 | } 261 | 262 | /** 263 | * Set the expiration time for the message. 264 | */ 265 | public function expiresAt(DateTime $expiresAt): self 266 | { 267 | $this->expiresAt = $expiresAt; 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * Set the collapse ID of the notification. 274 | */ 275 | public function collapseId(?string $collapseId): self 276 | { 277 | $this->collapseId = $collapseId; 278 | 279 | return $this; 280 | } 281 | 282 | /** 283 | * Set the APNS ID. 284 | */ 285 | public function apnsId(?string $apnsId): self 286 | { 287 | $this->apnsId = $apnsId; 288 | 289 | return $this; 290 | } 291 | 292 | /** 293 | * Set the message priority. 294 | */ 295 | public function priority(ApnMessagePriority $priority): self 296 | { 297 | $this->priority = $priority; 298 | 299 | return $this; 300 | } 301 | 302 | /** 303 | * Set a title-loc-key. 304 | */ 305 | public function titleLocKey(?string $titleLocKey = null): self 306 | { 307 | $this->titleLocKey = $titleLocKey; 308 | 309 | return $this; 310 | } 311 | 312 | /** 313 | * Set the title-loc-args. 314 | */ 315 | public function titleLocArgs(?array $titleLocArgs = null): self 316 | { 317 | $this->titleLocArgs = $titleLocArgs; 318 | 319 | return $this; 320 | } 321 | 322 | /** 323 | * Set an action-loc-key. 324 | */ 325 | public function actionLocKey($actionLocKey = null): self 326 | { 327 | $this->actionLocKey = $actionLocKey; 328 | 329 | return $this; 330 | } 331 | 332 | /** 333 | * Set a loc-key. 334 | */ 335 | public function setLocKey(?string $locKey): self 336 | { 337 | $this->locKey = $locKey; 338 | 339 | return $this; 340 | } 341 | 342 | /** 343 | * Set the loc-args. 344 | */ 345 | public function setLocArgs(?array $locArgs): self 346 | { 347 | $this->locArgs = $locArgs; 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * Add custom data to the notification. 354 | */ 355 | public function custom(string $key, mixed $value): self 356 | { 357 | $this->custom[$key] = $value; 358 | 359 | return $this; 360 | } 361 | 362 | /** 363 | * Sets custom alert value as JSON or Array. 364 | * 365 | * @param string|array $customAlert 366 | */ 367 | public function setCustomAlert($customAlert): self 368 | { 369 | $this->customAlert = $customAlert; 370 | 371 | return $this; 372 | } 373 | 374 | /** 375 | * Override the data of the notification. 376 | */ 377 | public function setCustom(array $custom): self 378 | { 379 | $this->custom = $custom; 380 | 381 | return $this; 382 | } 383 | 384 | /** 385 | * Add a URL argument to the notification. 386 | */ 387 | public function urlArg(string $key, mixed $value): self 388 | { 389 | $this->urlArgs[$key] = $value; 390 | 391 | return $this; 392 | } 393 | 394 | /** 395 | * Override the URL arguemnts of the notification. 396 | */ 397 | public function setUrlArgs(array $urlArgs): self 398 | { 399 | $this->urlArgs = $urlArgs; 400 | 401 | return $this; 402 | } 403 | 404 | /** 405 | * Add an action to the notification. 406 | */ 407 | public function action(string $action, mixed $params = null): self 408 | { 409 | return $this->custom('action', [ 410 | 'action' => $action, 411 | 'params' => $params, 412 | ]); 413 | } 414 | 415 | /** 416 | * Set message specific client. 417 | */ 418 | public function via(Client $client): self 419 | { 420 | $this->client = $client; 421 | 422 | return $this; 423 | } 424 | 425 | /** 426 | * Set mutable content value of the notification. 427 | */ 428 | public function mutableContent(?int $value = 1): self 429 | { 430 | $this->mutableContent = $value; 431 | 432 | return $this; 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/ApnMessageInterruptionLevel.php: -------------------------------------------------------------------------------- 1 | app->bind(AuthProviderInterface::class, function ($app) { 22 | $options = Arr::except($app['config']['broadcasting.connections.apn'], 'production'); 23 | 24 | return Arr::exists($options, 'certificate_path') ? Certificate::create($options) : Token::create($options); 25 | }); 26 | 27 | $this->app->bind(Client::class, function ($app) { 28 | return new Client($app->make(AuthProviderInterface::class), $app['config']['broadcasting.connections.apn.production']); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ApnVoipChannel.php: -------------------------------------------------------------------------------- 1 | routeNotificationFor('apn_voip', $notification); 15 | 16 | if (empty($tokens)) { 17 | return null; 18 | } 19 | 20 | $message = $notification->toApnVoip($notifiable); 21 | 22 | $client = $message->client ?? $this->factory->instance(); 23 | 24 | $responses = $this->sendNotifications($client, $message, $tokens); 25 | 26 | $this->dispatchEvents($notifiable, $notification, $responses); 27 | 28 | return $responses; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ApnVoipMessage.php: -------------------------------------------------------------------------------- 1 | cache->remember(Client::class, Carbon::now()->addMinutes(static::CACHE_MINUTES), function () { 33 | return $this->app->make(Client::class); 34 | }); 35 | } 36 | } 37 | --------------------------------------------------------------------------------