Server Broadcasting
Notifikasi realtime pada Panel membutuhkan tiga komponen yang berjalan secara terpisah: broadcaster yang dapat dihubungi browser, queue worker yang mengirim event ke broadcaster, dan Echo client yang sudah dikonfigurasi di bundle frontend. Halaman ini membahas sisi operasionalnya — apa yang harus dijalankan, apa yang harus dikonfigurasi, dan bagaimana mengetahui bagian mana yang belum tersedia. Gunakan halaman ini ketika menempatkan Reverb di belakang proxy, atau ketika toast realtime bekerja di lokal tetapi tidak pernah sampai di production.
Contoh minimal yang berfungsi
# .env, di server
BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=redis
REVERB_APP_ID=…
REVERB_APP_KEY=…
REVERB_APP_SECRET=…
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST=panel.example.com
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https2
3
4
5
6
7
8
9
10
11
12
13
14
15
Tiga process berikut harus berjalan dan sebaiknya semuanya dikelola oleh supervisor:
php artisan reverb:start --host=127.0.0.1 --port=8080
php artisan queue:work
php-fpm2
3
Kirim notifikasi ke diri sendiri untuk memastikan seluruh rantainya bekerja:
use App\Models\User;
use PandaPanel\Broadcasting\PanelNotification;
PanelNotification::dispatch(User::first(), 'Broadcasting works.', 'success');2
3
4
Rantai proses, satu per satu
| Bagian | Berjalan di mana | Jika tidak tersedia, gejalanya |
|---|---|---|
| Broadcaster dengan driver yang dapat dijangkau browser | config/broadcasting.php | broadcasting.enabled bernilai false pada Inertia props; browser tidak melakukan koneksi |
routes/channels.php dengan callback authorization user | application | /broadcasting/auth mengembalikan 403 |
| Queue worker | supervisor | socket terhubung, tetapi notifikasi tidak pernah sampai |
configureEcho() di bundle | resources/js/app.ts, setelah di-compile | console warning hanya di development; production diam tanpa error |
Masing-masing komponen sengaja gagal secara senyap ketika berdiri sendiri. Panel yang websocket-nya mati tetap dapat dirender, notifikasi persistent tetap tersimpan, dan flash toast tetap bekerja. Konsekuensinya, diagnosis dilakukan menggunakan checklist, bukan mengandalkan stack trace.
Gate di sisi server
Panel tidak akan mengirimkan nama channel ke frontend kecuali application memang memiliki broadcaster yang secara teknis dapat digunakan:
use PandaPanel\Support\BroadcastSupport;
BroadcastSupport::isConfigured(); // bool2
3
| Method | Signature | Mengembalikan false ketika |
|---|---|---|
isConfigured | static isConfigured(): bool | broadcasting.default tidak ada/kosong; connection yang dipilih tidak memiliki driver; atau driver adalah null maupun log |
null dianggap nonaktif. Driver log memang valid di Laravel, tetapi hanya menulis broadcast ke log dan tidak dapat disubscribe oleh Echo.
Credential sengaja tidak divalidasi di sini. Hanya broadcaster yang benar-benar dapat memastikan key/secret valid. Menolak koneksi hanya karena server menilai bentuk credential “mencurigakan” akan menghasilkan failure mode yang lebih sulit dipahami.
Gate ini dibuat karena sebelumnya server dapat mengirim nama channel, frontend kemudian memanggil echo(), lalu @laravel/echo-vue melempar Echo has not been configured dari dalam onMounted. Mount yang berhenti di tengah dapat memicu banyak warning seperti Slot "default" invoked outside of the render function ketika Inertia mengganti layout yang belum selesai dimount. Tidak satu pun warning tersebut menjelaskan bahwa broadcaster belum dikonfigurasi.
Dua kondisi tersebut digabungkan di SharePanelData:
if ($panel === null || ! $panel->hasBroadcasting() || ! BroadcastSupport::isConfigured()) {
return ['enabled' => false, 'channel' => null];
}
return [
'enabled' => true,
'channel' => $panel->getBroadcastChannel($request->user()),
];2
3
4
5
6
7
8
Intent per Panel
| Method | Signature | Default |
|---|---|---|
broadcasting | broadcasting(bool $broadcasting = true): self | aktif |
hasBroadcasting | hasBroadcasting(): bool | |
getBroadcastChannel | getBroadcastChannel(?Authenticatable $user): ?string | null jika broadcasting dimatikan atau tidak ada user yang login |
use PandaPanel\Core\Panel;
Panel::make('reports')
->path('reports')
->broadcasting(false); // panel ini tidak membutuhkan realtime notification2
3
4
5
Matikan secara eksplisit pada Panel yang memang disajikan di lingkungan yang tidak memiliki broadcaster. Membiarkannya aktif sebenarnya hampir tidak memiliki biaya karena tidak ada koneksi sampai suatu Page subscribe, tetapi konfigurasi eksplisit membuat shared prop menjelaskan intent Panel, bukan menebak kondisi environment.
Channel
use PandaPanel\Broadcasting\PanelNotification;
PanelNotification::channelFor($user); // 'App.Models.User.7'2
3
Nama channel dibangun di server agar frontend dan backend tidak dapat berbeda. Nama tersebut mengikuti konvensi default Laravel, bukan format khusus PandaBear. Artinya broadcast notification lain dari application untuk user yang sama juga dapat masuk melalui channel yang sama.
// routes/channels.php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel(
'App.Models.User.{id}',
static fn ($user, int|string $id): bool => hash_equals((string) $user->getAuthIdentifier(), (string) $id),
);2
3
4
5
6
7
Tanpa callback tersebut, setiap subscription private channel akan ditolak dengan 403 dari /broadcasting/auth, sehingga Panel tidak menerima apa pun. Ini adalah private channel, jadi penolakan merupakan default yang benar. channels.php yang belum dikonfigurasi adalah deployment mistake, bukan security hole.
Dua event
Kedua event mengimplementasikan ShouldBroadcast dan menggunakan nama broadcast yang sama, yaitu panel.notification, sehingga frontend hanya membutuhkan satu subscription dan satu listener.
use PandaPanel\Broadcasting\PanelNotification;
new PanelNotification(
user: $user,
message: 'Import finished.',
type: 'info', // 'success'|'info'|'warning'|'error'
url: '/admin/imports/…', // sesuatu yang dapat dibuka, misalnya hasil file dari job
urlLabel: 'Download',
);2
3
4
5
6
7
8
9
| Class | broadcastAs() | Payload |
|---|---|---|
PandaPanel\Broadcasting\PanelNotification | panel.notification | type, message, url, urlLabel |
PandaPanel\Notifications\PanelNotificationSent | panel.notification | payload notification, ditambah message dan persistent |
Keduanya bukan ShouldBroadcastNow. Dispatch event akan membuat queued job. Ini adalah fakta operasional paling penting pada halaman ini:
Tidak ada queue worker = tidak ada notifikasi realtime.
Menjalankan Reverb di production
Contoh Supervisor:
[program:reverb]
command=php /var/www/current/artisan reverb:start --host=127.0.0.1 --port=8080
directory=/var/www/current
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/reverb.log2
3
4
5
6
7
8
Di belakang nginx, TLS dihentikan pada proxy dan websocket connection di-upgrade:
location /app {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}2
3
4
5
6
7
8
9
10
Ada tiga hal environment yang harus dipahami secara terpisah karena masing-masing menghasilkan silent failure yang berbeda:
REVERB_HOSTdanVITE_REVERB_HOSTadalah dua alamat berbeda.REVERB_HOSTadalah alamat yang digunakan PHP untuk menjangkau server, misalnya127.0.0.1.VITE_REVERB_HOSTadalah alamat publik yang dihubungi browser.- Variable
VITE_*dimasukkan ke bundle pada build time. MengubahVITE_REVERB_HOSTberarti harus menjalankannpm run build, bukan hanya restart process. Jika tidak dibuild ulang, browser tetap mencoba host lama. - TLS melalui proxy membutuhkan
VITE_REVERB_SCHEME=httpsdanVITE_REVERB_PORT=443. Browser terhubung ke proxy publik, bukan langsung ke process Reverb.
Menggunakan Pusher atau Ably menggantikan process Reverb tetapi tidak mengubah behavior PandaBear lainnya. Gate, channel, dan event tetap sama.
Process yang perlu direstart saat deploy
| Process | Command |
|---|---|
| Queue worker | php artisan queue:restart |
| Reverb | restart melalui supervisor |
| Octane | php artisan octane:reload |
Reverb tidak memuat application code seperti worker, tetapi process ini memegang konfigurasi .env saat start. Karena itu perubahan REVERB_* memerlukan restart. Perubahan BROADCAST_CONNECTION juga memerlukan rebuild config cache.
Restart Reverb akan memutus seluruh websocket connection aktif. Browser akan reconnect, tetapi notifikasi yang dikirim saat gap tidak diterima siapa pun karena websocket bukan queue. Untuk informasi yang tidak boleh hilang gunakan ->persistent(), sehingga row database tetap tersedia di Notification Centre pada navigasi berikutnya.
Mendiagnosis rantai yang gagal secara senyap
use PandaPanel\Core\PanelManager;
use PandaPanel\Support\BroadcastSupport;
BroadcastSupport::isConfigured(); // apakah application memiliki broadcaster
app(PanelManager::class)->get('admin')->hasBroadcasting(); // apakah Panel menginginkan broadcasting
app(PanelManager::class)->get('admin')->getBroadcastChannel($user); // channel yang akan disubscribe2
3
4
5
6
| Gejala | Penyebab |
|---|---|
broadcasting.enabled bernilai false pada props | broadcaster belum dikonfigurasi, atau Panel memanggil ->broadcasting(false) |
enabled true, tetapi console development memperingatkan [panel] Realtime notifications are off… | configureEcho() belum dipanggil dari resources/js/app.ts |
/broadcasting/auth menghasilkan 403 | callback di routes/channels.php belum ada atau menolak user |
| Tidak ada apa pun, tidak ada error, tetapi bell count tetap benar ketika navigasi | queue worker tidak berjalan; job BroadcastEvent masih menunggu di queue |
Kasus terakhir paling umum di production. Bell count menjadi indikator penting karena penyimpanan database untuk persistent notification tidak bergantung pada broadcast queue. Jadi notification dapat sudah tersimpan walaupun event realtime belum pernah keluar dari queue.
Menjalankan Panel tanpa broadcaster
Sepenuhnya didukung dan merupakan keadaan default fresh install. Server mengirim channel: null, composable berhenti sebelum menyentuh Echo, dan browser tidak mencoba membuat connection.
Notification tetap bekerja, hanya tidak realtime:
- Notification
->persistent()tetap muncul di bell; unread count diperbarui pada setiap Panel request. - Flash toast tetap bekerja karena dibawa melalui HTTP response, bukan websocket.
Hal yang perlu diperhatikan
BROADCAST_CONNECTION=logterlihat seperti broadcaster tetapi sebenarnya tidak dapat disubscribe.BroadcastSupportmemperlakukannya sama sepertinull.- Config cache membekukan
BROADCAST_CONNECTION. Setelah mengubahnya, jalankan kembaliphp artisan config:cacheatauphp artisan optimize. - Perubahan
VITE_*memerlukan rebuild frontend. Value tersebut di-inline saat build. - Restart Reverb dapat menghilangkan notifikasi yang sedang lewat. Gunakan persistent notification untuk informasi penting.
- Horizontal scaling Reverb membutuhkan konfigurasi scaling Reverb sendiri. PandaBear tidak mengasumsikan jumlah node; framework hanya menentukan channel.
- Channel dibuat per user, bukan per Panel. Jika satu user membuka tiga Panel, notification account tersebut dapat diterima di ketiganya. Ini disengaja karena notification bell adalah milik account, bukan milik satu screen.
Lihat juga
- Production checklist, Queues, Monitoring
- Reverb and Echo setup — konfigurasi client secara detail
- Broadcasting, Channel authorization
- Queued notifications, Toast notifications
- Broadcast failures
- Frontend build — alasan
VITE_*merupakan build input