├── LICENSE ├── README.md ├── composer.json └── src ├── FcmChannel.php ├── FcmMessage.php └── FcmNotificationServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ben Wilkins 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel FCM Notification 2 | Laravel FCM (Firebase Cloud Messaging) Notification Channel 3 | 4 | [![GitHub tag](https://badgen.net/github/tag/benwilkins/laravel-fcm-notification)](https://github.com/benwilkins/laravel-fcm-notification/releases) 5 | [![Packagist](https://badgen.net/packagist/v/benwilkins/laravel-fcm-notification)](https://packagist.org/packages/benwilkins/laravel-fcm-notification) 6 | [![Downloads](https://badgen.net/packagist/dt/benwilkins/laravel-fcm-notification)](https://packagist.org/packages/benwilkins/laravel-fcm-notification) 7 | [![Build Status](https://travis-ci.com/benwilkins/laravel-fcm-notification.svg)](https://travis-ci.com/benwilkins/laravel-fcm-notification) 8 | [![License](https://badgen.net/packagist/license/benwilkins/laravel-fcm-notification)](https://packagist.org/packages/benwilkins/laravel-fcm-notification) 9 | 10 | Use this package to send push notifications via Laravel to Firebase Cloud Messaging. Laravel 5.5+ required. 11 | 12 | This package works only with [Legacy HTTP Server Protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref) 13 | 14 | ## Install 15 | 16 | This package can be installed through Composer. 17 | 18 | ``` bash 19 | composer require benwilkins/laravel-fcm-notification 20 | ``` 21 | 22 | If installing on < Laravel 5.5 then add the service provider: 23 | 24 | ```php 25 | // config/app.php 26 | 'providers' => [ 27 | ... 28 | Benwilkins\FCM\FcmNotificationServiceProvider::class, 29 | ... 30 | ]; 31 | ``` 32 | 33 | Add your Firebase API Key in `config/services.php`. 34 | 35 | ```php 36 | return [ 37 | 38 | ... 39 | ... 40 | /* 41 | * Add the Firebase API key 42 | */ 43 | 'fcm' => [ 44 | 'key' => env('FCM_SECRET_KEY') 45 | ] 46 | ]; 47 | ``` 48 | 49 | ## Example Usage 50 | 51 | Use Artisan to create a notification: 52 | 53 | ```bash 54 | php artisan make:notification SomeNotification 55 | ``` 56 | 57 | Return `[fcm]` in the `public function via($notifiable)` method of your notification: 58 | 59 | ```php 60 | public function via($notifiable) 61 | { 62 | return ['fcm']; 63 | } 64 | ``` 65 | 66 | Add the method `public function toFcm($notifiable)` to your notification, and return an instance of `FcmMessage`: 67 | 68 | ```php 69 | use Benwilkins\FCM\FcmMessage; 70 | 71 | ... 72 | 73 | public function toFcm($notifiable) 74 | { 75 | $message = new FcmMessage(); 76 | $message->content([ 77 | 'title' => 'Foo', 78 | 'body' => 'Bar', 79 | 'sound' => '', // Optional 80 | 'icon' => '', // Optional 81 | 'click_action' => '' // Optional 82 | ])->data([ 83 | 'param1' => 'baz' // Optional 84 | ])->priority(FcmMessage::PRIORITY_HIGH); // Optional - Default is 'normal'. 85 | 86 | return $message; 87 | } 88 | ``` 89 | 90 | When sending to specific device, make sure your notifiable entity has `routeNotificationForFcm` method defined: 91 | 92 | ```php 93 | /** 94 | * Route notifications for the FCM channel. 95 | * 96 | * @param \Illuminate\Notifications\Notification $notification 97 | * @return string 98 | */ 99 | public function routeNotificationForFcm($notification) 100 | { 101 | return $this->device_token; 102 | } 103 | ``` 104 | 105 | When sending to a topic, you may define so within the `toFcm` method in the notification: 106 | 107 | ```php 108 | use Benwilkins\FCM\FcmMessage; 109 | 110 | ... 111 | 112 | public function toFcm($notifiable) 113 | { 114 | $message = new FcmMessage(); 115 | $message->to('the-topic', $recipientIsTopic = true) 116 | ->content([...]) 117 | ->data([...]); 118 | 119 | return $message; 120 | } 121 | ``` 122 | 123 | Or when sending with a condition: 124 | 125 | ```php 126 | use Benwilkins\FCM\FcmMessage; 127 | 128 | ... 129 | 130 | public function toFcm($notifiable) 131 | { 132 | $message = new FcmMessage(); 133 | $message->contentAvailable(true) 134 | ->priority('high') 135 | ->condition("'user_".$notifiable->id."' in topics") 136 | ->data([...]); 137 | 138 | return $message; 139 | } 140 | ``` 141 | 142 | You may provide optional headers or override the request headers using `setHeaders()`: 143 | 144 | ```php 145 | use Benwilkins\FCM\FcmMessage; 146 | 147 | ... 148 | 149 | public function toFcm($notifiable) 150 | { 151 | $message = new FcmMessage(); 152 | $message->setHeaders([ 153 | 'project_id' => "48542497347" // FCM sender_id 154 | ])->content([ 155 | 'title' => 'Foo', 156 | 'body' => 'Bar', 157 | 'sound' => '', // Optional 158 | 'icon' => '', // Optional 159 | 'click_action' => '' // Optional 160 | ])->data([ 161 | 'param1' => 'baz' // Optional 162 | ])->priority(FcmMessage::PRIORITY_HIGH); // Optional - Default is 'normal'. 163 | 164 | return $message; 165 | } 166 | ``` 167 | 168 | ## Interpreting a Response 169 | 170 | To process any laravel notification channel response check [Laravel Notification Events](https://laravel.com/docs/6.0/notifications#notification-events) 171 | 172 | This channel return a json array response: 173 | ```json 174 | { 175 | "multicast_id": "number", 176 | "success": "number", 177 | "failure": "number", 178 | "canonical_ids": "number", 179 | "results": "array" 180 | } 181 | ``` 182 | 183 | Check [FCM Legacy HTTP Server Protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream) 184 | for response interpreting documentation. 185 | 186 | ## License 187 | 188 | The MIT License (MIT). Please see [License File](LICENSE) for more information. 189 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benwilkins/laravel-fcm-notification", 3 | "description": "Laravel FCM (Firebase Cloud Messaging) Notification Channel", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel", 7 | "fcm", 8 | "firebase", 9 | "notifications" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Ben Wilkins", 14 | "email": "bentwilkins@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.6.4", 19 | "guzzlehttp/guzzle": "^6.2|^7.0", 20 | "illuminate/notifications": "~5.3|^6.0|^7.0|^8.0|^9.0", 21 | "illuminate/queue": "~5.3|^6.0|^7.0|^8.0|^9.0", 22 | "illuminate/support": "~5.3|^6.0|^7.0|^8.0|^9.0", 23 | "illuminate/config": "~5.3|^6.0|^7.0|^8.0|^9.0" 24 | }, 25 | "require-dev": { 26 | "mockery/mockery": "~1.0", 27 | "phpunit/phpunit": "~6.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Benwilkins\\FCM\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Benwilkins\\FCM\\Tests\\": "tests" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "Benwilkins\\FCM\\FcmNotificationServiceProvider" 43 | ] 44 | } 45 | }, 46 | "scripts": { 47 | "test": "phpunit" 48 | }, 49 | "config": { 50 | "sort-packages": true, 51 | "preferred-install": "dist" 52 | }, 53 | "minimum-stability": "dev", 54 | "prefer-stable": true 55 | } 56 | -------------------------------------------------------------------------------- /src/FcmChannel.php: -------------------------------------------------------------------------------- 1 | client = $client; 34 | $this->apiKey = $apiKey; 35 | } 36 | 37 | /** 38 | * @param mixed $notifiable 39 | * @param Notification $notification 40 | * @return mixed 41 | */ 42 | public function send($notifiable, Notification $notification) 43 | { 44 | /** @var FcmMessage $message */ 45 | $message = $notification->toFcm($notifiable); 46 | 47 | if (is_null($message->getTo()) && is_null($message->getCondition())) { 48 | if (! $to = $notifiable->routeNotificationFor('fcm', $notification)) { 49 | return; 50 | } 51 | 52 | $message->to($to); 53 | } 54 | 55 | $response_array = []; 56 | 57 | if (is_array($message->getTo())) { 58 | $chunks = array_chunk($message->getTo(), 1000); 59 | 60 | foreach ($chunks as $chunk) { 61 | $message->to($chunk); 62 | 63 | $response = $this->client->post(self::API_URI, [ 64 | 'headers' => [ 65 | 'Authorization' => 'key='.$this->apiKey, 66 | 'Content-Type' => 'application/json', 67 | ], 68 | 'body' => $message->formatData(), 69 | ]); 70 | 71 | array_push($response_array, \GuzzleHttp\json_decode($response->getBody(), true)); 72 | } 73 | } else { 74 | $response = $this->client->post(self::API_URI, [ 75 | 'headers' => [ 76 | 'Authorization' => 'key='.$this->apiKey, 77 | 'Content-Type' => 'application/json', 78 | ], 79 | 'body' => $message->formatData(), 80 | ]); 81 | 82 | array_push($response_array, \GuzzleHttp\json_decode($response->getBody(), true)); 83 | } 84 | 85 | return $response_array; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/FcmMessage.php: -------------------------------------------------------------------------------- 1 | to = '/topics/'.$recipient; 79 | } elseif (is_array($recipient) && count($recipient) == 1) { 80 | $this->to = $recipient[0]; 81 | } else { 82 | $this->to = $recipient; 83 | } 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * @return string|array|null 90 | */ 91 | public function getTo() 92 | { 93 | return $this->to; 94 | } 95 | 96 | /** 97 | * The notification object to send to FCM. `title` and `body` are required. 98 | * @param array $params ['title' => '', 'body' => '', 'sound' => '', 'icon' => '', 'click_action' => ''] 99 | * @return $this 100 | */ 101 | public function content(array $params) 102 | { 103 | $this->notification = $params; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * @param array|null $data 110 | * @return $this 111 | */ 112 | public function data($data = null) 113 | { 114 | $this->data = $data; 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @param string $priority 121 | * @return $this 122 | */ 123 | public function priority($priority) 124 | { 125 | $this->priority = $priority; 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function getCondition() 134 | { 135 | return $this->condition; 136 | } 137 | 138 | /** 139 | * @param string $condition 140 | * @return $this 141 | */ 142 | public function condition($condition) 143 | { 144 | $this->condition = $condition; 145 | 146 | return $this; 147 | } 148 | 149 | /** 150 | * @return string 151 | */ 152 | public function getCollapseKey() 153 | { 154 | return $this->collapseKey; 155 | } 156 | 157 | /** 158 | * @param string $collapseKey 159 | * @return $this 160 | */ 161 | public function collapseKey($collapseKey) 162 | { 163 | $this->collapseKey = $collapseKey; 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * @return bool 170 | */ 171 | public function isContentAvailable() 172 | { 173 | return $this->contentAvailable; 174 | } 175 | 176 | /** 177 | * @param bool $contentAvailable 178 | * @return $this 179 | */ 180 | public function contentAvailable($contentAvailable) 181 | { 182 | $this->contentAvailable = $contentAvailable; 183 | 184 | return $this; 185 | } 186 | 187 | /** 188 | * @return bool 189 | */ 190 | public function isMutableContent() 191 | { 192 | return $this->mutableContent; 193 | } 194 | 195 | /** 196 | * @param bool $mutableContent 197 | * @return $this 198 | */ 199 | public function mutableContent($mutableContent) 200 | { 201 | $this->mutableContent = $mutableContent; 202 | 203 | return $this; 204 | } 205 | 206 | /** 207 | * @return int 208 | */ 209 | public function getTimeToLive() 210 | { 211 | return $this->timeToLive; 212 | } 213 | 214 | /** 215 | * @param int $timeToLive 216 | * @return $this 217 | */ 218 | public function timeToLive($timeToLive) 219 | { 220 | $this->timeToLive = $timeToLive; 221 | 222 | return $this; 223 | } 224 | 225 | /** 226 | * @return bool 227 | */ 228 | public function isDryRun() 229 | { 230 | return $this->dryRun; 231 | } 232 | 233 | /** 234 | * @param bool $dryRun 235 | * @return $this 236 | */ 237 | public function dryRun($dryRun) 238 | { 239 | $this->dryRun = $dryRun; 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * @return string 246 | */ 247 | public function getPackageName() 248 | { 249 | return $this->packageName; 250 | } 251 | 252 | /** 253 | * @param string $packageName 254 | * @return $this 255 | */ 256 | public function packageName($packageName) 257 | { 258 | $this->packageName = $packageName; 259 | 260 | return $this; 261 | } 262 | 263 | /** 264 | * @return string 265 | */ 266 | public function formatData() 267 | { 268 | $payload = [ 269 | 'priority' => $this->priority, 270 | ]; 271 | 272 | if (is_array($this->to)) { 273 | $payload['registration_ids'] = $this->to; 274 | } elseif (! empty($this->to)) { 275 | $payload['to'] = $this->to; 276 | } 277 | 278 | if (isset($this->data) && count($this->data)) { 279 | $payload['data'] = $this->data; 280 | } 281 | 282 | if (isset($this->notification) && count($this->notification)) { 283 | $payload['notification'] = $this->notification; 284 | } 285 | 286 | if (isset($this->condition) && ! empty($this->condition)) { 287 | $payload['condition'] = $this->condition; 288 | } 289 | 290 | if (isset($this->collapseKey) && ! empty($this->collapseKey)) { 291 | $payload['collapse_key'] = $this->collapseKey; 292 | } 293 | 294 | if (isset($this->contentAvailable)) { 295 | $payload['content_available'] = $this->contentAvailable; 296 | } 297 | 298 | if (isset($this->mutableContent)) { 299 | $payload['mutable_content'] = $this->mutableContent; 300 | } 301 | 302 | if (isset($this->timeToLive)) { 303 | $payload['time_to_live'] = $this->timeToLive; 304 | } 305 | 306 | if (isset($this->dryRun)) { 307 | $payload['dry_run'] = $this->dryRun; 308 | } 309 | 310 | if (isset($this->packageName) && ! empty($this->packageName)) { 311 | $payload['restricted_package_name'] = $this->packageName; 312 | } 313 | 314 | return \GuzzleHttp\json_encode($payload); 315 | } 316 | 317 | /** 318 | * @param array $headers 319 | * @return $this 320 | */ 321 | public function setHeaders($headers = []) 322 | { 323 | $this->headers = $headers; 324 | 325 | return $this; 326 | } 327 | 328 | /** 329 | * @return array 330 | */ 331 | public function getHeaders() 332 | { 333 | return $this->headers; 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/FcmNotificationServiceProvider.php: -------------------------------------------------------------------------------- 1 | extend('fcm', function () { 22 | return new FcmChannel(app(Client::class), config('services.fcm.key')); 23 | }); 24 | }); 25 | } 26 | } 27 | --------------------------------------------------------------------------------