Kegagalan broadcasting
Notification realtime pada panel membutuhkan empat bagian yang harus bekerja bersama: broadcaster di PHP, callback authorization channel, Echo di browser, dan queue worker. Setiap bagian gagal dengan gejala yang berbeda, dan tidak ada yang selalu gagal secara mencolok. Halaman ini membantu Anda menentukan bagian mana yang belum terpasang atau tidak bekerja.
Mulai dari sini
Tanyakan langsung kepada server melalui tinker:
use App\Models\User;
use PandaPanel\Core\PanelManager;
use PandaPanel\Support\BroadcastSupport;
BroadcastSupport::isConfigured(); // has this app a broadcaster
app(PanelManager::class)->get('admin')->hasBroadcasting(); // does this panel want one
app(PanelManager::class)->get('admin')->getBroadcastChannel($user); // what it will subscribe to2
3
4
5
6
7
Lalu kirim satu notification ke diri sendiri dan perhatikan alurnya:
use App\Models\User;
use PandaPanel\Broadcasting\PanelNotification;
PanelNotification::dispatch(User::query()->first(), 'Broadcasting works.', 'success');2
3
4
php artisan queue:work # a ShouldBroadcast event is a queued job
php artisan reverb:start # development needs the websocket server running2
Tabel gejala
| Gejala | Penyebab |
|---|---|
broadcasting.enabled bernilai false pada Inertia props | tidak ada broadcaster yang dapat dipakai, atau panel memanggil ->broadcasting(false) |
broadcasting.enabled bernilai true, tetapi dev console memperingatkan [panel] Realtime notifications are off… | configureEcho() belum pernah dipanggil di browser |
Echo has not been configured dilempar dari onMounted, lalu muncul rangkaian warning Slot "default" invoked outside of the render function | perilaku pada versi lama; upgrade, atau lihat bagian di bawah |
403 dari /broadcasting/auth di network tab | routes/channels.php tidak memiliki callback untuk App.Models.User.{id}, atau callback menolak user ini |
| Tidak terjadi apa pun dan tidak ada error | queue worker tidak berjalan; broadcast job masih menunggu di queue |
| Toast muncul dua kali | response sudah membawa toast yang sama — gunakan ->broadcast(false) pada notification |
1. Apakah aplikasi memiliki broadcaster
PandaPanel\Support\BroadcastSupport menjawab pertanyaan yang sebelumnya tidak dijawab oleh konfigurasi panel. Panel::broadcasting() hanya menyatakan apakah panel menginginkan notification realtime; helper ini memeriksa apakah aplikasi memang memiliki mekanisme yang dapat mengirimkannya ke browser.
use PandaPanel\Support\BroadcastSupport;
BroadcastSupport::isConfigured(); // bool2
3
| Method | Signature | Mengembalikan |
|---|---|---|
isConfigured | static isConfigured(): bool | apakah browser secara realistis dapat terhubung ke broadcaster aplikasi |
Ada tiga syarat, dan method ini mengembalikan false jika salah satunya tidak terpenuhi:
broadcasting.defaultmenunjuk sebuah connection. Laravel baru tanpaconfig/broadcasting.phpbelum memilikinya.- Connection tersebut benar-benar ada dan memiliki driver. Nama connection yang tidak pernah didefinisikan adalah typo, bukan broadcaster.
- Driver tersebut dapat menjangkau browser. Driver
nulldanlogdikecualikan: keduanya valid sebagai driver, tetapi tidak menyediakan channel yang bisa di-subscribe oleh Echo.
use Illuminate\Support\Facades\Config;
Config::set('broadcasting.default', 'reverb');
Config::set('broadcasting.connections.reverb.driver', 'reverb');
BroadcastSupport::isConfigured(); // true2
3
4
5
6
Yang sengaja tidak diperiksa adalah validitas credentials. Hanya broadcaster yang benar-benar dapat memverifikasinya, dan panel yang menolak mencoba koneksi hanya karena key terlihat tidak meyakinkan justru akan menghasilkan failure mode yang lebih buruk.
BROADCAST_CONNECTION=reverbPerilaku yang perlu diketahui: aplikasi yang melakukan broadcast dari PHP tetapi memiliki BROADCAST_CONNECTION=null atau log tidak mendapatkan panel channel. Connection tersebut memang sejak awal tidak dapat di-subscribe browser; perbedaannya sekarang adalah panel dapat menyatakan kondisi itu dengan jelas daripada gagal saat mount.
2. Apakah panel menginginkan broadcasting
$panel->broadcasting(false);| Member | Signature | Default |
|---|---|---|
broadcasting | Panel::broadcasting(bool $broadcasting = true): self | aktif |
hasBroadcasting | Panel::hasBroadcasting(): bool | true |
getBroadcastChannel | Panel::getBroadcastChannel(?Authenticatable $user): ?string | nama private channel, atau null |
getBroadcastChannel() mengembalikan null ketika broadcasting dimatikan atau tidak ada user yang login. Dengan begitu frontend tidak menerima channel yang pada akhirnya akan ditolak.
Tidak ada koneksi yang dibuat sampai sebuah komponen benar-benar melakukan subscribe. Karena itu, broadcasting(false) benar-benar berarti tidak membuka koneksi, bukan membuka koneksi lalu mengabaikan event-nya.
3. Data yang dikirim ke frontend
SharePanelData membagikan satu prop yang dihitung pada setiap request:
'broadcasting' => ['enabled' => bool, 'channel' => string|null]Kedua pertanyaan harus sama-sama menjawab ya: panel menginginkan broadcasting, dan BroadcastSupport::isConfigured() menyatakan aplikasi mampu mengirimkannya. Verifikasi melalui test:
use Illuminate\Support\Facades\Config;
use Inertia\Testing\AssertableInertia;
it('tells the frontend which channel to listen on', function (): void {
Config::set('broadcasting.default', 'reverb');
Config::set('broadcasting.connections.reverb.driver', 'reverb');
$this->actingAs($this->admin)
->get('/admin')
->assertInertia(fn (AssertableInertia $page) => $page
->where('broadcasting.enabled', true)
->where('broadcasting.channel', 'App.Models.User.'.$this->admin->getKey()));
});2
3
4
5
6
7
8
9
10
11
12
13
Channel dikirim per request, bukan disimpan di definisi panel, karena nilainya bergantung pada siapa yang sedang mengakses. Panel::toSharedArray() memang tidak memiliki key broadcasting, dan ada test yang memastikan kontrak tersebut.
4. Event
use PandaPanel\Broadcasting\PanelNotification;
PanelNotification::dispatch($user, 'Export finished', 'success');
PanelNotification::dispatch(
$user,
'Your export is ready',
'success',
url: route('panel.admin.export-file', ['file' => $file, 'exporter' => $exporter]),
urlLabel: 'Download',
);2
3
4
5
6
7
8
9
10
11
public function __construct(
public readonly Authenticatable $user,
public readonly string $message,
public readonly string $type = 'info',
public readonly ?string $url = null,
public readonly ?string $urlLabel = null,
) {}2
3
4
5
6
7
| Member | Signature | Nilai |
|---|---|---|
channelFor | static channelFor(Authenticatable $user): string | 'App.Models.User.'.$user->getAuthIdentifier() |
broadcastOn | broadcastOn(): array | satu PrivateChannel dengan nama tersebut |
broadcastAs | broadcastAs(): string | 'panel.notification' |
broadcastWith | broadcastWith(): array | ['type' => …, 'message' => …, 'url' => …, 'urlLabel' => …] |
$type harus salah satu dari success, info, warning, atau error. Nilai lain dibuang oleh guard di client dan tidak dirender.
channelFor() membangun nama channel di server agar sisi server dan client tidak dapat keluar sinkron. Format ini juga mengikuti nama channel default Laravel, sehingga notification yang dibroadcast oleh bagian lain dari aplikasi dapat tiba di channel user yang sama.
5. Authorization channel
Private channel akan ditolak jika tidak memiliki callback authorization. Channel ini bukan konsep khusus PandaBear:
// routes/channels.php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel(
'App.Models.User.{id}',
static fn ($user, int|string $id): bool => (int) $user->id === (int) $id,
);2
3
4
5
6
7
Tanpa callback tersebut browser menerima 403 dari /broadcasting/auth. Socket dapat terhubung, tetapi event tidak pernah sampai. php artisan install:broadcasting membuat file tersebut dan mendaftarkannya di bootstrap/app.php.
6. Echo di browser
Panel mengimpor echo() dari @laravel/echo-vue, yang akan melempar exception sampai configureEcho() dipanggil:
// resources/js/app.ts
import { configureEcho } from '@laravel/echo-vue';
configureEcho({ broadcaster: 'reverb' });2
3
4
Panel tidak membaca window.Echo. install:broadcasting membuat resources/js/echo.js yang mengisi window.Echo; itu adalah client berbeda. Helper echo() memakai instance tingkat module yang hanya dibuat oleh configureEcho().
Mount cascade
usePanelBroadcasting() membungkus setiap pemanggilan echo(). Jika Echo belum dikonfigurasi, composable memberi warning satu kali, hanya pada development, lalu panel tetap melanjutkan proses render:
[panel] Realtime notifications are off: this panel has broadcasting enabled and a broadcaster
configured, but Echo was never set up in the browser. Call configureEcho({ broadcaster: … })
in resources/js/app.ts, or turn it off with ->broadcasting(false) on the panel.2
3
Wrapper ini dibuat karena failure mode sebelumnya sangat buruk dan jauh dari penyebab sebenarnya: echo() melempar exception dari dalam onMounted pada shell panel, mount yang terhenti kemudian menghasilkan banyak warning Slot "default" invoked outside of the render function ketika Inertia mengganti layout yang belum selesai mount, dan tidak ada satu pun pesan yang menyebut broadcaster.
Composable melakukan subscribe ke .panel.notification — leading dot menandakan custom event name — lalu memvalidasi payload sebelum menampilkan apa pun. Payload websocket sudah melewati boundary yang sama seperti HTTP response. type di luar empat nilai yang diizinkan dibuang; payload tanpa message juga dibuang.
7. Queue
PanelNotification mengimplementasikan ShouldBroadcast, sehingga dispatch event tersebut membuat queued job. Tanpa worker, socket dapat terlihat terhubung tetapi tetap diam:
php artisan queue:workIni adalah failure yang hampir tidak memiliki gejala: tidak ada console message, tidak ada network request tambahan, dan tidak ada log. Karena itu, periksa queue worker lebih awal ketika konfigurasi lain tampak benar.
Mengirim melalui notification API
PandaPanel\Notifications\Notification menggabungkan toast dengan database notification:
use PandaPanel\Notifications\Notification;
use PandaPanel\Notifications\NotificationAction;
Notification::make('export-ready')
->title('Your export is ready')
->body('1,204 records')
->success()
->persistent() // also write a row, so it can be read later
->broadcast(false) // the response already carries a toast
->actions([
NotificationAction::make('download')->label('Download')->url($url),
])
->send($user);2
3
4
5
6
7
8
9
10
11
12
13
->broadcast(false) adalah perbaikan ketika pesan tampil dua kali. Export action sudah menaruh toast pada response yang dikembalikan, sehingga mengirim teks yang sama lagi melalui websocket akan menggandakannya.
Menjalankan panel tanpa broadcaster
Sepenuhnya didukung dan merupakan kondisi default pada instalasi baru. SharePanelData mengirim channel: null, usePanelBroadcasting keluar sebelum menyentuh Echo, dan tidak ada koneksi yang dicoba. Notification tetap bekerja, hanya tidak realtime:
- Notification dengan
->persistent()masuk ke notification center, dan unread count dibawa pada setiap request panel. - Flash toast tetap bekerja karena ikut pada response, bukan melalui socket.
Pada panel yang memang tidak akan pernah menggunakan broadcaster, nyatakan secara eksplisit agar shared prop merefleksikan intent, bukan sekadar konfigurasi environment:
$panel->broadcasting(false);Catatan
- Development membutuhkan
php artisan reverb:start. Tanpanya browser terus mencoba reconnect di background dan panel tetap berfungsi, hanya tanpa live notification. - Variable
VITE_*dikompilasi ke dalam bundle. Mengubah salah satunya membutuhkan rebuild, bukan hanya restart server. REVERB_HOSTdanVITE_REVERB_HOSTbiasanya berbeda alamat: yang pertama adalah alamat yang dipakai PHP untuk mencapai server, yang kedua adalah alamat yang dihubungi browser.pusher-jsmerupakan dependency terpisah.@laravel/echo-vuemengimpornya sebagai transport untuk broadcasterreverbdanpusher; bundle tidak akan berhasil dibangun tanpanya.- Guest tidak mendapatkan channel.
getBroadcastChannel(null)mengembalikannull, sehingga login screen tidak membuka socket. - Di luar panel tidak ada broadcasting prop yang perlu digunakan:
enabledbernilaifalsedanchannelbernilainullpada setiap non-panel request, termasuk halaman starter kit. - Persistent notification memicu
panel:notificationpadawindowsebelum toast, sehingga unread count pada bell langsung benar walaupun toast segera ditutup.