started working in notifications

This commit is contained in:
Ghostie 2025-01-09 21:16:27 -05:00
parent fd2f08ca78
commit 1baabb7649
27 changed files with 1718 additions and 21 deletions

View File

@ -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}"

10
TODO.md
View File

@ -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

View File

@ -0,0 +1,62 @@
<?php
namespace App\Events\AP;
use App\Models\Activity;
use App\Models\Actor;
use App\Types\TypeActivity;
use App\Types\TypeActor;
use App\Events\UserUnfollowedEvent;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ActivityUndoEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $activity;
public $actor;
public $object;
/**
* Create a new event instance.
*/
public function __construct($activity)
{
$this->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 ();
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use App\Models\Actor;
use App\Models\Activity;
use App\Models\Note;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NoteLikedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public Activity $activity;
public Actor $actor;
public Note $note;
/**
* Create a new event instance.
*/
public function __construct(Activity $activity, Actor $actor, Note $note)
{
$this->activity = $activity;
$this->actor = $actor;
$this->note = $note;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use App\Models\Activity;
use App\Models\Actor;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserFollowedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public Activity $activity;
public Actor $actor;
public Actor $object;
/**
* Create a new event instance.
*/
public function __construct($activity, $actor, $object)
{
$this->activity = $activity;
$this->actor = $actor;
$this->object = $object;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Events;
use App\Models\Activity;
use App\Models\Actor;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserUnfollowedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public Activity $activity;
public Actor $actor;
public Actor $object;
/**
* Create a new event instance.
*/
public function __construct(Activity $activity, Actor $actor, Actor $object)
{
$this->activity = $activity;
$this->actor = $actor;
$this->object = $object;
}
}

View File

@ -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);
}

View File

@ -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"]);
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Listeners;
use App\Models\Like;
use App\Events\NoteLikedEvent;
use Illuminate\Support\Facades\Log;
use App\Notifications\UserNotification;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class NoteLikedListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(NoteLikedEvent $event): void
{
$like = Like::create ([
"activity_id" => $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
));
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Listeners;
use App\Events\UserFollowedEvent;
use App\Models\Follow;
use App\Models\Actor;
use App\Models\Activity;
use App\Notifications\UserNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class UserFollowedListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(UserFollowedEvent $event): void
{
Follow::create ([
"activity_id" => $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
));
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Listeners;
use App\Models\Actor;
use App\Models\Follow;
use App\Notifications\UserNotification;
use App\Events\UserUnfollowedEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class UserUnfollowedListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(UserUnfollowedEvent $event): void
{
$follow_exists = Follow::where("actor_id", $event->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
));
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class UserNotification extends Notification
{
use Queueable;
public $type;
public $actor;
public $object;
public $activity;
/**
* Create a new notification instance.
*/
public function __construct($type, $actor, $object, $activity = null)
{
$this->type = $type;
$this->actor = $actor;
$this->object = $object;
$this->activity = $activity;
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['database', 'broadcast'];
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
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,
];
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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"

1000
composer.lock generated

File diff suppressed because it is too large Load Diff

82
config/broadcasting.php Normal file
View File

@ -0,0 +1,82 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
*/
'default' => 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',
],
],
];

92
config/reverb.php Normal file
View File

@ -0,0 +1,92 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Reverb Server
|--------------------------------------------------------------------------
|
| This option controls the default server used by Reverb to handle
| incoming messages as well as broadcasting message to all your
| connected clients. At this time only "reverb" is supported.
|
*/
'default' => 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),
],
],
],
];

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('notifications', function (Blueprint $table) {
$table->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');
}
};

26
package-lock.json generated
View File

@ -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",

View File

@ -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"
}

Binary file not shown.

View File

@ -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';

14
resources/js/echo.js Normal file
View File

@ -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'],
});

View File

@ -41,5 +41,28 @@
</div>
@vite(["resources/js/app.js"])
@if (auth ()->check ())
<script>
const notification_sound = new Audio ("/resources/sounds/notification.mp3")
notification_sound.muted = true
notification_sound.play ()
notification_sound.muted = false
function register_echo ()
{
window.Echo.private ("App.Models.User.{{ auth ()->id () }}")
.notification ((notification) =>
{
notification_sound.play ()
// of course, remove this
alert ("You received a " + notification.notification_type + " notification!")
})
}
document.addEventListener ("DOMContentLoaded", register_echo)
</script>
@endif
</body>
</html>

7
routes/channels.php Normal file
View File

@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});

9
test.php Normal file
View File

@ -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);