├── src ├── Http │ ├── routes.php │ └── Controllers │ │ └── SSEController.php ├── Facades │ └── SSEFacade.php ├── Helpers │ └── helper.php ├── Config │ └── config.php ├── SSE.php ├── Migrations │ └── 2018_09_12_99999_create_sselogs_table.php ├── Models │ └── SSELog.php ├── Views │ └── view.blade.php └── ServiceProvider.php ├── license.md ├── composer.json └── readme.md /src/Http/routes.php: -------------------------------------------------------------------------------- 1 | 'Sarfraznawaz2005\SSE\Http\Controllers', 6 | 'prefix' => 'sse' 7 | ], 8 | static function () { 9 | 10 | Route::get('sse_stream', 'SSEController@stream')->name('__sse_stream__'); 11 | } 12 | ); 13 | -------------------------------------------------------------------------------- /src/Facades/SSEFacade.php: -------------------------------------------------------------------------------- 1 | notify($message, $type, $event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Config/config.php: -------------------------------------------------------------------------------- 1 | env('SSE_ENABLED', true), 7 | 8 | // polling interval in seconds between requests 9 | 'interval' => env('SSE_INTERVAL', 15), 10 | 11 | // append logged user id in SSE response 12 | 'append_user_id' => env('SSE_APPEND_USER_ID', true), 13 | 14 | // keep events log in database 15 | 'keep_events_logs' => env('SSE_KEEP_EVENTS_LOGS', false), 16 | 17 | // notification settings 18 | 'position' => 'bottomRight', // top, topLeft, topCenter, topRight, center, centerLeft, centerRight, bottom, bottomLeft, bottomCenter, bottomRight 19 | 'timeout' => false, // false, 1000, 3000, 3500, etc. Delay for closing event in milliseconds (ms). Set 'false' for sticky notifications. 20 | ]; 21 | -------------------------------------------------------------------------------- /src/SSE.php: -------------------------------------------------------------------------------- 1 | SSELog = $SSELog; 17 | } 18 | 19 | /** 20 | * Notify SSE event. 21 | * 22 | * @param string $message : notification message 23 | * @param string $type : alert, success, error, warning, info 24 | * @param string $event : Type of event such as "EmailSent", "UserLoggedIn", etc 25 | * @return bool 26 | */ 27 | public function notify($message, $type = 'info', $event = 'message'): bool 28 | { 29 | return $this->SSELog->saveEvent($message, $type, $event); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Migrations/2018_09_12_99999_create_sselogs_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id')->default(0); 19 | $table->string('message'); 20 | $table->string('event', 50); 21 | $table->string('type', 50); 22 | $table->enum('delivered', [0, 1])->default(0); 23 | $table->string('client', 50)->nullable(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('sselogs'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Sarfraz Ahmed 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sarfraznawaz2005/laravel-sse", 3 | "description": "Laravel package to provide Server Sent Events functionality for your app.", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Sarfraz Ahmed", 9 | "email": "sarfraznawaz2005@gmail.com", 10 | "homepage": "http://codeinphp.github.io" 11 | } 12 | ], 13 | "homepage": "https://github.com/sarfraznawaz2005/laravel-sse", 14 | "keywords": [ 15 | "Laravel", 16 | "SSE", 17 | "Sever Sent Events", 18 | "Server", 19 | "Event", 20 | "Realtime", 21 | "Polling", 22 | "Notifications" 23 | ], 24 | "require": { 25 | "php": "^7.0|^8.0", 26 | "illuminate/support": "~5|~6|~7|~8|~9|~10|~11" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Sarfraznawaz2005\\SSE\\": "src/" 31 | }, 32 | "files": [ 33 | "src/Helpers/helper.php" 34 | ] 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "Sarfraznawaz2005\\SSE\\ServiceProvider" 40 | ], 41 | "aliases": { 42 | "SSE": "Sarfraznawaz2005\\SSE\\Facades\\SSEFacade" 43 | } 44 | } 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | }, 49 | "minimum-stability": "stable" 50 | } 51 | -------------------------------------------------------------------------------- /src/Models/SSELog.php: -------------------------------------------------------------------------------- 1 | deleteProcessed(); 30 | 31 | $data['message'] = $message; 32 | $data['event'] = $event; 33 | $data['type'] = $type; 34 | 35 | if (config('sse.append_user_id') && auth()->check()) { 36 | $data['user_id'] = auth()->user()->getAuthIdentifier(); 37 | } 38 | 39 | $this->fill($data); 40 | 41 | return $this->save(); 42 | } 43 | 44 | /** 45 | * Deletes already processed events 46 | */ 47 | public function deleteProcessed() 48 | { 49 | if (!config('sse.keep_events_logs')) { 50 | $this->where('delivered', '1')->delete(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Views/view.blade.php: -------------------------------------------------------------------------------- 1 | @if(config('sse.enabled')) 2 | 3 | 4 | 5 | 6 | {{-- EventSource pollyfill --}} 7 | 8 | 9 | 41 | @endif 42 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->routesAreCached()) { 17 | require __DIR__ . '/Http/routes.php'; 18 | } 19 | 20 | $this->loadViewsFrom(__DIR__ . '/Views', 'sse'); 21 | 22 | // Publishing is only necessary when using the CLI. 23 | if ($this->app->runningInConsole()) { 24 | // Publish the configuration file. 25 | $this->publishes([ 26 | __DIR__ . '/Config/config.php' => config_path('sse.php'), 27 | ], 'sse.config'); 28 | 29 | // Publish the views. 30 | $this->publishes([ 31 | __DIR__ . '/Views' => base_path('resources/views/vendor/sse'), 32 | ], 'sse.views'); 33 | 34 | // Publish the migrations. 35 | $this->publishes([ 36 | __DIR__ . '/Migrations' => database_path('migrations') 37 | ]); 38 | } 39 | } 40 | 41 | /** 42 | * Register package services. 43 | * 44 | * @return void 45 | */ 46 | public function register() 47 | { 48 | $this->mergeConfigFrom(__DIR__ . '/Config/config.php', 'sse'); 49 | 50 | // Register the service the package provides. 51 | $this->app->singleton('SSE', function () { 52 | return $this->app->make(SSE::class); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Latest Version on Packagist][ico-version]][link-packagist] 2 | [![Total Downloads][ico-downloads]][link-downloads] 3 | 4 | # Laravel SSE 5 | 6 | Laravel package to provide Server Sent Events functionality for your app. You can use this package to show instant notifications to your users without them having to refresh their pages. 7 | 8 | ## Requirements 9 | 10 | - PHP >= 7 11 | - Laravel 5 12 | 13 | ## Installation 14 | 15 | Via Composer 16 | 17 | ``` bash 18 | $ composer require sarfraznawaz2005/laravel-sse 19 | ``` 20 | 21 | For Laravel < 5.5: 22 | 23 | Add Service Provider to `config/app.php` in `providers` section 24 | ```php 25 | Sarfraznawaz2005\SSE\ServiceProvider::class, 26 | ``` 27 | 28 | Add Facade to `config/app.php` in `aliases` section 29 | ```php 30 | 'SSE' => Sarfraznawaz2005\SSE\Facades\SSEFacade::class, 31 | ``` 32 | 33 | 34 | --- 35 | 36 | Publish package's config, migration and view files by running below command: 37 | 38 | ```bash 39 | $ php artisan vendor:publish --provider="Sarfraznawaz2005\SSE\ServiceProvider" 40 | ``` 41 | Run `php artisan migrate` to create `sselogs` table. 42 | 43 | ## Setup SSE 44 | 45 | Setup config options in `config/sse.php` file and then add this in your view/layout file: 46 | 47 | ```php 48 | @include('sse::view') 49 | ``` 50 | 51 | ## Usage 52 | 53 | Syntax: 54 | ```php 55 | /** 56 | * @param string $message : notification message 57 | * @param string $type : alert, success, error, warning, info 58 | * @param string $event : Type of event such as "EmailSent", "UserLoggedIn", etc 59 | */ 60 | SSEFacade::notify($message, $type = 'info', $event = 'message') 61 | ``` 62 | 63 | To show popup notifications on the screen, in your controllers/event classes, you can do: 64 | 65 | ```php 66 | use Sarfraznawaz2005\SSE\Facades\SSEFacade; 67 | 68 | public function myMethod() 69 | { 70 | SSEFacade::notify('hello world....'); 71 | 72 | // or via helper 73 | sse_notify('hi there'); 74 | } 75 | ``` 76 | 77 | ## Customizing Notification Library 78 | 79 | By default, package uses [noty](https://github.com/needim/noty) for showing notifications. You can customize this by modifying code in `resources/views/vendor/sse/view.blade.php` file. 80 | 81 | ## Customizing SSE Events 82 | 83 | By default, pacakge uses `message` event type for streaming response: 84 | 85 | 86 | ```php 87 | SSEFacade::notify($message, $type = 'info', $event = 'message') 88 | ``` 89 | 90 | Notice `$event = 'message'`. You can customize this, let's say you want to use `UserLoggedIn` as SSE event type: 91 | 92 | ```php 93 | use Sarfraznawaz2005\SSE\Facades\SSEFacade; 94 | 95 | public function myMethod() 96 | { 97 | SSEFacade::notify('hello world....', 'info', 'UserLoggedIn'); 98 | 99 | // or via helper 100 | sse_notify('hi there', 'info', 'UserLoggedIn'); 101 | } 102 | ``` 103 | 104 | Then you need to handle this in your view yourself like this: 105 | 106 | ```javascript 107 | 116 | ``` 117 | 118 | ## Credits 119 | 120 | - [Sarfraz Ahmed][link-author] 121 | - [All Contributors][link-contributors] 122 | 123 | ## License 124 | 125 | Please see the [license file](license.md) for more information. 126 | 127 | [ico-version]: https://img.shields.io/packagist/v/sarfraznawaz2005/laravel-sse.svg?style=flat-square 128 | [ico-downloads]: https://img.shields.io/packagist/dt/sarfraznawaz2005/laravel-sse.svg?style=flat-square 129 | 130 | [link-packagist]: https://packagist.org/packages/sarfraznawaz2005/laravel-sse 131 | [link-downloads]: https://packagist.org/packages/sarfraznawaz2005/laravel-sse 132 | [link-author]: https://github.com/sarfraznawaz2005 133 | [link-contributors]: https://github.com/sarfraznawaz2005/laravel-sse/graphs/contributors 134 | -------------------------------------------------------------------------------- /src/Http/Controllers/SSEController.php: -------------------------------------------------------------------------------- 1 | headers->set('Content-Type', 'text/event-stream'); 24 | $response->headers->set('Cache-Control', 'no-cache'); 25 | $response->headers->set('Connection', 'keep-alive'); 26 | $response->headers->set('X-Accel-Buffering', 'no'); 27 | 28 | // delete expired/old 29 | $this->deleteOld($SSELog); 30 | 31 | $response->setCallback(function () use ($SSELog) { 32 | 33 | // if the connection has been closed by the client we better exit the loop 34 | if (connection_aborted()) { 35 | return; 36 | } 37 | 38 | $model = $SSELog->where('delivered', '0')->oldest()->first(); 39 | 40 | echo ':' . str_repeat(' ', 2048) . "\n"; // 2 kB padding for IE 41 | echo "retry: 5000\n"; 42 | 43 | if (!$model) { 44 | // no new data to send 45 | echo ": heartbeat\n\n"; 46 | } else { 47 | 48 | $clientId = $this->getClientId(); 49 | 50 | // check if we have notified this client 51 | $clientModel = $SSELog 52 | ->where('message', $model->message) 53 | ->where('client', $clientId) 54 | ->first(); 55 | 56 | if ($clientModel) { 57 | // no new data to send 58 | echo ": heartbeat\n\n"; 59 | } else { 60 | 61 | $data = json_encode([ 62 | 'message' => $model->message, 63 | 'type' => strtolower($model->type), 64 | 'time' => date('H:i:s A', strtotime($model->created_at)), 65 | ]); 66 | 67 | echo 'id: ' . $model->id . "\n"; 68 | echo 'event: ' . $model->event . "\n"; 69 | echo 'data: ' . $data . "\n\n"; 70 | 71 | $clientModel = new $SSELog(); 72 | $clientModel->user_id = $model->user_id; 73 | $clientModel->message = $model->message; 74 | $clientModel->event = $model->event; 75 | $clientModel->type = $model->type; 76 | $clientModel->client = $clientId; 77 | $clientModel->delivered = '1'; 78 | $clientModel->save(); 79 | } 80 | } 81 | 82 | ob_flush(); 83 | flush(); 84 | 85 | sleep(config('sse.interval')); 86 | }); 87 | 88 | return $response->send(); 89 | } 90 | 91 | /** 92 | * Tries to identify different SSE connections 93 | * 94 | * @return string 95 | */ 96 | protected function getClientId(): string 97 | { 98 | return md5(php_uname('n') . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']); 99 | } 100 | 101 | /** 102 | * @param SSELog $SSELog 103 | * @throws \Exception 104 | */ 105 | public function deleteOld(SSELog $SSELog) 106 | { 107 | $date = new DateTime; 108 | $date->modify('-' . (config('sse.interval') * 2) . ' seconds'); 109 | 110 | // delete client-specific records 111 | $SSELog 112 | ->where('created_at', '<=', $date->format('Y-m-d H:i:s')) 113 | ->where('client', '!=', '') 114 | ->delete(); 115 | 116 | // update actual message as delivered 117 | $SSELog 118 | ->where('created_at', '<=', $date->format('Y-m-d H:i:s')) 119 | ->update(['delivered' => '1']); 120 | } 121 | } 122 | --------------------------------------------------------------------------------