diff --git a/.env.example b/.env.example index 7d50a2d..ea0ef23 100644 --- a/.env.example +++ b/.env.example @@ -36,7 +36,7 @@ SESSION_ENCRYPT=false SESSION_PATH=/ SESSION_DOMAIN=null -BROADCAST_CONNECTION=log +BROADCAST_CONNECTION=reverb FILESYSTEM_DISK=local QUEUE_CONNECTION=database @@ -66,3 +66,15 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +REVERB_APP_ID= +REVERB_APP_KEY= +REVERB_APP_SECRET= +REVERB_HOST="localhost" +REVERB_PORT=8080 +REVERB_SCHEME=http + +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" +VITE_REVERB_HOST="${REVERB_HOST}" +VITE_REVERB_PORT="${REVERB_PORT}" +VITE_REVERB_SCHEME="${REVERB_SCHEME}" diff --git a/TODO.md b/TODO.md index 8858320..33ef283 100644 --- a/TODO.md +++ b/TODO.md @@ -16,10 +16,10 @@ - [x] Boosts - [x] Local Boost - [x] Tags - - [ ] Mentions - - [ ] Local mentions + - [x] Mentions + - [x] Local mentions - [ ] Private post - - [ ] Pinned Posts + - [x] Pinned Posts - [x] Nodeinfo - [ ] Notifications @@ -31,7 +31,7 @@ - [x] Update profile picture - [ ] Mark account as private (in federation manual approval is needed) - [x] Allow custom CSS - - [ ] Profile audio + - [x] Profile audio - [ ] Top 8 friends - [x] Friends (they are known as follows in the activitypub protocol) - [x] Add friends @@ -67,4 +67,4 @@ - [x] Use jobs when posting activities - [ ] Sign the get activities for mastodon when secure mode is enable - [x] Set a minimum font size for the tags cloud - - [ ] Pagination, pagination, AND MORE PAGINATION + - [x] Pagination, pagination, AND MORE PAGINATION diff --git a/app/Events/AP/ActivityUndoEvent.php b/app/Events/AP/ActivityUndoEvent.php new file mode 100644 index 0000000..f5e94a2 --- /dev/null +++ b/app/Events/AP/ActivityUndoEvent.php @@ -0,0 +1,62 @@ +activity = $activity; + + $this->actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]); + + $child_activity = $activity ["object"]; + $child_activity_id = ""; + + if (is_array ($child_activity)) + $child_activity_id = $child_activity ["id"]; + else + $child_activity_id = $child_activity; + + if (!TypeActivity::activity_exists ($child_activity_id)) + return ["error" => "Activity not found",]; + + $child_activity = Activity::where ("activity_id", $child_activity_id)->first (); + $this->object = $child_activity; + + switch ($this->object->type) + { + case "Follow": + $unfollowed_actor = Actor::where ("actor_id", $this->object->object)->first (); + UserUnfollowedEvent::dispatch ($this->object, $this->actor, $unfollowed_actor); + break; + } + + $child_activity->delete (); + } +} diff --git a/app/Events/NoteLikedEvent.php b/app/Events/NoteLikedEvent.php new file mode 100644 index 0000000..19aa12a --- /dev/null +++ b/app/Events/NoteLikedEvent.php @@ -0,0 +1,34 @@ +activity = $activity; + $this->actor = $actor; + $this->note = $note; + } +} diff --git a/app/Events/UserFollowedEvent.php b/app/Events/UserFollowedEvent.php new file mode 100644 index 0000000..1014757 --- /dev/null +++ b/app/Events/UserFollowedEvent.php @@ -0,0 +1,34 @@ +activity = $activity; + $this->actor = $actor; + $this->object = $object; + } +} diff --git a/app/Events/UserUnfollowedEvent.php b/app/Events/UserUnfollowedEvent.php new file mode 100644 index 0000000..32514ee --- /dev/null +++ b/app/Events/UserUnfollowedEvent.php @@ -0,0 +1,33 @@ +activity = $activity; + $this->actor = $actor; + $this->object = $object; + } +} diff --git a/app/Http/Controllers/AP/APInboxController.php b/app/Http/Controllers/AP/APInboxController.php index 6f4b1a4..ef43602 100644 --- a/app/Http/Controllers/AP/APInboxController.php +++ b/app/Http/Controllers/AP/APInboxController.php @@ -13,6 +13,11 @@ use App\Models\Like; use App\Types\TypeActor; use App\Types\TypeActivity; +use App\Events\UserFollowedEvent; +use App\Events\NoteLikedEvent; + +use App\Events\AP\ActivityUndoEvent; + use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use App\Http\Controllers\Controller; @@ -66,14 +71,9 @@ class APInboxController extends Controller return response ()->json (["error" => "Follow already exists",], 409); $activity ["activity_id"] = $activity ["id"]; - $act = Activity::create ($activity); - $follow = Follow::create ([ - "activity_id" => $act->id, - "actor" => $actor->id, - "object" => $target->id, - ]); + UserFollowedEvent::dispatch ($act, $actor, $target); // TODO: Users should be able to manually check this $accept_activity = TypeActivity::craft_accept ($act); @@ -88,6 +88,7 @@ class APInboxController extends Controller public function handle_undo (User $user, $activity) { + ActivityUndoEvent::dispatch ($activity, $activity); return response ()->json (ActionsActivity::activity_undo ($activity)); } @@ -114,11 +115,7 @@ class APInboxController extends Controller else $act = Activity::where ("activity_id", $activity ["id"])->first (); - $like = Like::create ([ - "activity_id" => $act->id, - "actor_id" => $actor->id, - "note_id" => $note->id, - ]); + NoteLikedEvent::dispatch ($act, $actor, $note); return response ()->json (["success" => "Like created",], 200); } diff --git a/app/Http/Controllers/AP/APInstanceInboxController.php b/app/Http/Controllers/AP/APInstanceInboxController.php index 8d5ef37..0aa201b 100644 --- a/app/Http/Controllers/AP/APInstanceInboxController.php +++ b/app/Http/Controllers/AP/APInstanceInboxController.php @@ -2,20 +2,23 @@ namespace App\Http\Controllers\AP; -use App\Actions\ActionsActivity; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use App\Models\Actor; use App\Models\Activity; use App\Models\Announcement; +use App\Models\ProfilePin; use App\Types\TypeActor; use App\Types\TypeActivity; use App\Types\TypeNote; +use App\Actions\ActionsActivity; + use App\Http\Controllers\Controller; -use App\Models\ProfilePin; + +use App\Notifications\UserNotification; class APInstanceInboxController extends Controller { @@ -93,6 +96,12 @@ class APInstanceInboxController extends Controller "note_id" => $note_exists->id ]); + $note_actor = $note_exists->get_actor ()->first (); + if ($note_actor->user) + { + $note_actor->user->notify (new UserNotification("Boost", $announcement_actor, $note_exists)); + } + return response ()->json (["status" => "ok"]); } diff --git a/app/Listeners/NoteLikedListener.php b/app/Listeners/NoteLikedListener.php new file mode 100644 index 0000000..a6f799f --- /dev/null +++ b/app/Listeners/NoteLikedListener.php @@ -0,0 +1,46 @@ + $event->activity->id, + "actor_id" => $event->actor->id, + "note_id" => $event->note->id + ]); + + $user = $event->note->get_actor ()->first ()->user; + if (!$user) + return; + + $user->notify (new UserNotification( + "Like", + $event->actor->id, + $event->note->id, + $event->activity->id + )); + } +} diff --git a/app/Listeners/UserFollowedListener.php b/app/Listeners/UserFollowedListener.php new file mode 100644 index 0000000..cae76de --- /dev/null +++ b/app/Listeners/UserFollowedListener.php @@ -0,0 +1,49 @@ + $event->activity->id, + "actor" => $event->actor->id, + "object" => $event->object->id + ]); + + $user = $event->object->user; + if (!$user) + return; + + $user->notify (new UserNotification ( + "Follow", + $event->actor->id, + $event->object->id, + $event->activity->id + )); + } +} diff --git a/app/Listeners/UserUnfollowedListener.php b/app/Listeners/UserUnfollowedListener.php new file mode 100644 index 0000000..0abdb9e --- /dev/null +++ b/app/Listeners/UserUnfollowedListener.php @@ -0,0 +1,48 @@ +actor->id) + ->where("object_id", $event->object->id) + ->first(); + + if ($follow_exists) + $follow_exists->delete (); + + $user = $event->object->user; + if (!$user) + return; + + $user->notify(new UserNotification( + "Unfollow", + $event->actor->id, + $event->object->id, + $event->activity->id + )); + } +} diff --git a/app/Notifications/UserNotification.php b/app/Notifications/UserNotification.php new file mode 100644 index 0000000..6e38602 --- /dev/null +++ b/app/Notifications/UserNotification.php @@ -0,0 +1,65 @@ +type = $type; + $this->actor = $actor; + $this->object = $object; + $this->activity = $activity; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + return ['database', 'broadcast']; + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + 'type' => $this->type, + 'actor' => $this->actor, + 'object' => $this->object, + 'activity' => $this->activity, + ]; + } + + public function toBroadcast ($notifiable) + { + // we don't really need to broadcast any information + return [ + "notification_type" => $this->type, + "actor" => $this->actor, + "object" => $this->object, + "activity" => $this->activity, + ]; + } +} diff --git a/app/Types/TypeNote.php b/app/Types/TypeNote.php index b5eeafb..e36fc31 100644 --- a/app/Types/TypeNote.php +++ b/app/Types/TypeNote.php @@ -8,6 +8,7 @@ use App\Models\Actor; use App\Models\Activity; use App\Models\NoteAttachment; use App\Models\NoteMention; +use App\Notifications\UserNotification; use GuzzleHttp\Client; use Illuminate\Support\Facades\Log; @@ -187,19 +188,23 @@ class TypeNote $mention_actor = null; $actor_exists = null; - Log::info (json_encode ($exploded_name)); + $is_local = false; if (count ($exploded_name) == 2) { // let's check if maybe it's local $actor_exists = Actor::where ("preferredUsername", $exploded_name [1])->first (); if (!$actor_exists) continue; + + $is_local = true; } else if (count ($exploded_name) == 3) { // maybe it's remote $actor_exists = TypeActor::actor_exists_or_obtain_from_handle($exploded_name [1], $exploded_name [2]); + if ($actor_exists->user) + $is_local = true; } else continue; @@ -208,6 +213,15 @@ class TypeNote "note_id" => $note->id, "actor_id" => $actor_exists->id ]); + + if ($is_local) + { + $actor_exists->user->notify (new UserNotification( + "Mention", + $actor, + $actor_exists + )); + } break; } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 3afe501..e6a7b5b 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -10,6 +10,7 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', + channels: __DIR__.'/../routes/channels.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { diff --git a/composer.json b/composer.json index 72112bb..98da26e 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "guzzlehttp/guzzle": "^7.9", "intervention/image": "^3.10", "laravel/framework": "^11.31", + "laravel/reverb": "^1.0", "laravel/tinker": "^2.9", "league/html-to-markdown": "^5.1", "predis/predis": "^2.3" diff --git a/composer.lock b/composer.lock index 8aa745e..5448bd5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "86986bb5e6245e2f122938332d651d64", + "content-hash": "85664129adb9aca87569d3e717e34c4f", "packages": [ { "name": "brick/math", @@ -135,6 +135,136 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "clue/redis-protocol", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/clue/redis-protocol.git", + "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/redis-protocol/zipball/6f565332f5531b7722d1e9c445314b91862f6d6c", + "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\Redis\\Protocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A streaming Redis protocol (RESP) parser and serializer written in pure PHP.", + "homepage": "https://github.com/clue/redis-protocol", + "keywords": [ + "parser", + "protocol", + "redis", + "resp", + "serializer", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/redis-protocol/issues", + "source": "https://github.com/clue/redis-protocol/tree/v0.3.2" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2024-08-07T11:06:28+00:00" + }, + { + "name": "clue/redis-react", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-redis.git", + "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-redis/zipball/84569198dfd5564977d2ae6a32de4beb5a24bdca", + "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca", + "shasum": "" + }, + "require": { + "clue/redis-protocol": "^0.3.2", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.0 || ^1.1", + "react/promise-timer": "^1.11", + "react/socket": "^1.16" + }, + "require-dev": { + "clue/block-react": "^1.5", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Redis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Async Redis client implementation, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-redis", + "keywords": [ + "async", + "client", + "database", + "reactphp", + "redis" + ], + "support": { + "issues": "https://github.com/clue/reactphp-redis/issues", + "source": "https://github.com/clue/reactphp-redis/tree/v2.8.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2025-01-03T16:18:33+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -663,6 +793,53 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -1625,6 +1802,88 @@ }, "time": "2024-11-12T14:59:47+00:00" }, + { + "name": "laravel/reverb", + "version": "v1.4.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/reverb.git", + "reference": "22eab51c56c9a06e26a892b57e9b9d392e4e1da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/reverb/zipball/22eab51c56c9a06e26a892b57e9b9d392e4e1da0", + "reference": "22eab51c56c9a06e26a892b57e9b9d392e4e1da0", + "shasum": "" + }, + "require": { + "clue/redis-react": "^2.6", + "guzzlehttp/psr7": "^2.6", + "illuminate/console": "^10.47|^11.0", + "illuminate/contracts": "^10.47|^11.0", + "illuminate/http": "^10.47|^11.0", + "illuminate/support": "^10.47|^11.0", + "laravel/prompts": "^0.1.15|^0.2.0|^0.3.0", + "php": "^8.2", + "pusher/pusher-php-server": "^7.2", + "ratchet/rfc6455": "^0.3.1", + "react/promise-timer": "^1.10", + "react/socket": "^1.14", + "symfony/console": "^6.0|^7.0", + "symfony/http-foundation": "^6.3|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.0", + "phpstan/phpstan": "^1.10", + "ratchet/pawl": "^0.4.1", + "react/async": "^4.2", + "react/http": "^1.9" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Output": "Laravel\\Reverb\\Output" + }, + "providers": [ + "Laravel\\Reverb\\ApplicationManagerServiceProvider", + "Laravel\\Reverb\\ReverbServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Reverb\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Joe Dixon", + "email": "joe@laravel.com" + } + ], + "description": "Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.", + "keywords": [ + "WebSockets", + "laravel", + "real-time", + "websocket" + ], + "support": { + "issues": "https://github.com/laravel/reverb/issues", + "source": "https://github.com/laravel/reverb/tree/v1.4.4" + }, + "time": "2024-12-06T18:56:46+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.1", @@ -2894,6 +3153,97 @@ ], "time": "2024-11-21T10:39:51+00:00" }, + { + "name": "paragonie/sodium_compat", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb", + "shasum": "" + }, + "require": { + "php": "^8.1", + "php-64bit": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9", + "vimeo/psalm": "^4|^5" + }, + "suggest": { + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0" + }, + "time": "2024-09-04T12:51:01+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -3570,6 +3920,67 @@ }, "time": "2024-12-10T01:58:33+00:00" }, + { + "name": "pusher/pusher-php-server", + "version": "7.2.7", + "source": { + "type": "git", + "url": "https://github.com/pusher/pusher-http-php.git", + "reference": "148b0b5100d000ed57195acdf548a2b1b38ee3f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/148b0b5100d000ed57195acdf548a2b1b38ee3f7", + "reference": "148b0b5100d000ed57195acdf548a2b1b38ee3f7", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.2", + "paragonie/sodium_compat": "^1.6|^2.0", + "php": "^7.3|^8.0", + "psr/log": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "overtrue/phplint": "^2.3", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Pusher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Library for interacting with the Pusher REST API", + "keywords": [ + "events", + "messaging", + "php-pusher-server", + "publish", + "push", + "pusher", + "real time", + "real-time", + "realtime", + "rest", + "trigger" + ], + "support": { + "issues": "https://github.com/pusher/pusher-http-php/issues", + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.7" + }, + "time": "2025-01-06T10:56:20+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -3795,6 +4206,593 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "ratchet/rfc6455", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2 || ^1.7", + "php": ">=5.4.2" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "react/socket": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" + }, + "time": "2021-12-09T23:20:49+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/promise-timer", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7.0 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/reactphp/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-04T14:27:45+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..ebc3fb9 --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,82 @@ + env('BROADCAST_CONNECTION', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over WebSockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'reverb' => [ + 'driver' => 'reverb', + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/reverb.php b/config/reverb.php new file mode 100644 index 0000000..3fead53 --- /dev/null +++ b/config/reverb.php @@ -0,0 +1,92 @@ + env('REVERB_SERVER', 'reverb'), + + /* + |-------------------------------------------------------------------------- + | Reverb Servers + |-------------------------------------------------------------------------- + | + | Here you may define details for each of the supported Reverb servers. + | Each server has its own configuration options that are defined in + | the array below. You should ensure all the options are present. + | + */ + + 'servers' => [ + + 'reverb' => [ + 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'), + 'port' => env('REVERB_SERVER_PORT', 8080), + 'hostname' => env('REVERB_HOST'), + 'options' => [ + 'tls' => [], + ], + 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000), + 'scaling' => [ + 'enabled' => env('REVERB_SCALING_ENABLED', false), + 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'), + 'server' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'port' => env('REDIS_PORT', '6379'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'database' => env('REDIS_DB', '0'), + ], + ], + 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15), + 'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Reverb Applications + |-------------------------------------------------------------------------- + | + | Here you may define how Reverb applications are managed. If you choose + | to use the "config" provider, you may define an array of apps which + | your server will support, including their connection credentials. + | + */ + + 'apps' => [ + + 'provider' => 'config', + + 'apps' => [ + [ + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'allowed_origins' => ['*'], + 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60), + 'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30), + 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000), + ], + ], + + ], + +]; diff --git a/database/migrations/2025_01_10_012709_create_notifications_table.php b/database/migrations/2025_01_10_012709_create_notifications_table.php new file mode 100644 index 0000000..d738032 --- /dev/null +++ b/database/migrations/2025_01_10_012709_create_notifications_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; diff --git a/package-lock.json b/package-lock.json index d200175..afe6858 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "autoprefixer": "^10.4.20", "axios": "^1.7.4", "concurrently": "^9.0.1", + "laravel-echo": "^1.17.1", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.47", + "pusher-js": "^8.4.0-rc2", "tailwindcss": "^3.4.13", "vite": "^6.0" } @@ -1585,6 +1587,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/laravel-echo": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.1.tgz", + "integrity": "sha512-ORWc4vDfnBj/Oe5ThZ5kYyGItRjLDqAQUyhD/7UhehUOqc+s5x9HEBjtMVludNMP6VuXw6t7Uxt8bp63kaTofg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/laravel-vite-plugin": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz", @@ -1994,6 +2005,15 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "node_modules/pusher-js": { + "version": "8.4.0-rc2", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz", + "integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2434,6 +2454,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/package.json b/package.json index 0d10472..a6d106b 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "autoprefixer": "^10.4.20", "axios": "^1.7.4", "concurrently": "^9.0.1", + "laravel-echo": "^1.17.1", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.47", + "pusher-js": "^8.4.0-rc2", "tailwindcss": "^3.4.13", "vite": "^6.0" } diff --git a/public/resources/sounds/notification.mp3 b/public/resources/sounds/notification.mp3 new file mode 100644 index 0000000..a1ad34a Binary files /dev/null and b/public/resources/sounds/notification.mp3 differ diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 5c8b9ed..ecd4a7a 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -15,3 +15,11 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; }) } })() + +/** + * Echo exposes an expressive API for subscribing to channels and listening + * for events that are broadcast by Laravel. Echo and event broadcasting + * allow your team to quickly build robust real-time web applications. + */ + +import './echo'; diff --git a/resources/js/echo.js b/resources/js/echo.js new file mode 100644 index 0000000..9349afa --- /dev/null +++ b/resources/js/echo.js @@ -0,0 +1,14 @@ +import Echo from 'laravel-echo'; + +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: 'reverb', + key: import.meta.env.VITE_REVERB_APP_KEY, + wsHost: import.meta.env.VITE_REVERB_HOST, + wsPort: import.meta.env.VITE_REVERB_PORT ?? 80, + wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, + forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + enabledTransports: ['ws', 'wss'], +}); diff --git a/resources/views/partials/layout.blade.php b/resources/views/partials/layout.blade.php index 7735c5f..ec2578a 100644 --- a/resources/views/partials/layout.blade.php +++ b/resources/views/partials/layout.blade.php @@ -41,5 +41,28 @@ @vite(["resources/js/app.js"]) + + @if (auth ()->check ()) + + @endif diff --git a/routes/channels.php b/routes/channels.php new file mode 100644 index 0000000..df2ad28 --- /dev/null +++ b/routes/channels.php @@ -0,0 +1,7 @@ +id === (int) $id; +}); diff --git a/test.php b/test.php new file mode 100644 index 0000000..4e88e9e --- /dev/null +++ b/test.php @@ -0,0 +1,9 @@ +use App\Models\Actor; +use App\Models\User; + +use App\Events\UserFollowed; + +$actor = Actor::find(3); +$user = User::find(1); + +UserFollowed::dispatch ($actor, $user);