├── 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 |
--------------------------------------------------------------------------------