# Laravel Integration Guide

Vask is a drop-in Pusher replacement for Laravel Broadcasting and Laravel Echo. The fastest way in is the `vask/laravel` Composer package — it OAuths into your account, writes the `PUSHER_*` credentials to `.env`, and verifies the connection.

## Installation

### Recommended: the `vask/laravel` package

```bash
composer require vask/laravel
php artisan vask:install
```

What `vask:install` does:

- Runs the OAuth device flow (you approve a short code in your browser — no git config or local tokens).
- Writes `PUSHER_APP_ID`, `PUSHER_APP_KEY`, `PUSHER_APP_SECRET`, `PUSHER_HOST`, `PUSHER_PORT`, `PUSHER_SCHEME`, and `PUSHER_APP_CLUSTER` to your `.env`.
- Runs `vask:doctor` afterwards to confirm everything connects.

Source: [github.com/vask-dev/laravel](https://github.com/vask-dev/laravel) · [packagist.org/packages/vask/laravel](https://packagist.org/packages/vask/laravel).

### Manual install

Prefer to wire it up by hand (CI, custom env management, audit)? Add your Vask app credentials to `.env`:

```env
BROADCAST_CONNECTION=pusher

PUSHER_APP_ID=your_app_key
PUSHER_APP_KEY=your_app_key
PUSHER_APP_SECRET=your_app_secret
PUSHER_HOST=wss.vask.dev
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=vask
```

Then follow [Laravel's broadcasting docs](https://laravel.com/docs/broadcasting#pusher-channels) for the rest of the standard setup.

## `vask:doctor`

After install (or any time you suspect drift), run the diagnostic:

```bash
php artisan vask:doctor
php artisan vask:doctor --no-ping --no-broadcast   # skip live network checks
```

It validates your `PUSHER_*` config, optionally pings `wss.vask.dev`, and optionally fires a test broadcast end-to-end. Source: [github.com/vask-dev/laravel](https://github.com/vask-dev/laravel).

## Demo route

`vask/laravel` ships a local-only page at `/_vask/demo`. Start your dev server, visit it, click an emoji, and watch the round-trip (Laravel → Vask → browser) — including latency — without writing any frontend code. It exercises both server-side broadcasts and Pusher client events.

The route only registers when `app()->environment() === 'local'`. Disable it entirely with:

```env
VASK_NO_DEMO=true
```

## Broadcasting Events

### Creating a Broadcast Event

Create an event that implements `ShouldBroadcast`:

```php
<?php

namespace App\Events;

use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Message $message,
        public string $userName
    ) {}

    public function broadcastOn(): Channel
    {
        return new Channel('chat.' . $this->message->room_id);
    }

    public function broadcastAs(): string
    {
        return 'message.sent';
    }

    public function broadcastWith(): array
    {
        return [
            'id' => $this->message->id,
            'content' => $this->message->content,
            'user' => $this->userName,
            'created_at' => $this->message->created_at->toISOString(),
        ];
    }
}
```

### Triggering Events

Broadcast events from your controllers or anywhere in your application:

```php
use App\Events\MessageSent;

// In a controller method
public function sendMessage(Request $request)
{
    $message = Message::create([
        'room_id' => $request->room_id,
        'user_id' => auth()->id(),
        'content' => $request->content,
    ]);

    // Broadcast the event
    broadcast(new MessageSent(
        $message,
        auth()->user()->name
    ));

    return response()->json($message);
}

// Or use the event() helper
event(new MessageSent($message, $userName));

// For immediate broadcasting (bypass queue)
broadcast(new MessageSent($message, $userName))->toOthers();
```

## Client-Side Setup

### Configure Laravel Echo

Initialize Echo in your `bootstrap.js` or `app.{js,ts,tsx}` file:

```javascript
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: 'vask',
    wsHost: 'wss.vask.dev',
    forceTLS: true,
    enabledTransports: ['ws'],
});
```

### Listening to Events

Subscribe to channels and listen for events in your components:

```javascript
// Listen to a public channel
Echo.channel('chat.1').listen('.message.sent', (event) => {
    console.log('New message:', event);
    // Update your UI with the new message
    addMessageToChat(event);
});

// Listen to multiple events
Echo.channel('notifications')
    .listen('.user.joined', (e) => {
        console.log(e.userName + ' joined');
    })
    .listen('.user.left', (e) => {
        console.log(e.userName + ' left');
    });

// Leave a channel
Echo.leave('chat.1');
```

## Private Channels

### Authorization Routes

Define channel authorization logic in `routes/channels.php`:

```php
<?php

use App\Models\User;
use App\Models\Room;
use Illuminate\Support\Facades\Broadcast;

// Private channel authorization
Broadcast::channel('room.{roomId}', function (User $user, int $roomId) {
    // Return true if user can access this room
    return $user->rooms()->where('room_id', $roomId)->exists();
});

// Return data with authorization
Broadcast::channel('user.{userId}', function (User $user, int $userId) {
    if ($user->id === $userId) {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'avatar' => $user->avatar_url,
        ];
    }
    return false;
});
```

### Broadcasting to Private Channels

```php
use Illuminate\Broadcasting\PrivateChannel;

class OrderStatusUpdated implements ShouldBroadcast
{
    public function broadcastOn(): Channel
    {
        return new PrivateChannel('user.' . $this->order->user_id);
    }
}
```

### Listening to Private Channels

```javascript
// Subscribe to a private channel
Echo.private('user.' + userId)
    .listen('.order.updated', (event) => {
        console.log('Order updated:', event.order);
    })
    .listen('.payment.received', (event) => {
        showNotification('Payment received: $' + event.amount);
    });
```

## Presence Channels

Presence channels let you track who's online in real-time.

### Authorization with User Data

```php
Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'avatar' => $user->avatar_url,
        ];
    }
});
```

### Using Presence Channels

```php
use Illuminate\Broadcasting\PresenceChannel;

class UserJoinedRoom implements ShouldBroadcast
{
    public function broadcastOn(): Channel
    {
        return new PresenceChannel('room.' . $this->roomId);
    }
}
```

### Client-Side Presence

```javascript
let onlineUsers = [];

Echo.join('room.' + roomId)
    .here((users) => {
        // Initial users in the channel
        console.log('Users online:', users);
        onlineUsers = users;
        updateUsersList(users);
    })
    .joining((user) => {
        // User joined the channel
        console.log(user.name + ' joined');
        onlineUsers.push(user);
        addUserToList(user);
    })
    .leaving((user) => {
        // User left the channel
        console.log(user.name + ' left');
        onlineUsers = onlineUsers.filter((u) => u.id !== user.id);
        removeUserFromList(user);
    })
    .error((error) => {
        console.error('Connection error:', error);
    })
    .listen('.message.sent', (event) => {
        // Listen to events on presence channel
        addMessageToChat(event);
    });
```

## Webhooks

Vask sends webhooks for channel, presence, and client events. The `vask/laravel` package gives you typed payloads and auto-registers `POST /webhooks/vask` the first time it sees a handler — no handler, no route.

Register handlers in a service provider's `boot()`:

```php
use Vask\Laravel\Facades\Vask;
use Vask\Laravel\Webhooks\Payloads\ChannelOccupiedPayload;
use Vask\Laravel\Webhooks\Payloads\ChannelVacatedPayload;
use Vask\Laravel\Webhooks\Payloads\MemberAddedPayload;
use Vask\Laravel\Webhooks\Payloads\MemberRemovedPayload;
use Vask\Laravel\Webhooks\Payloads\ClientEventPayload;

public function boot(): void
{
    Vask::onChannelOccupied(fn (ChannelOccupiedPayload $event) => /* ... */);
    Vask::onChannelVacated(fn (ChannelVacatedPayload $event) => /* ... */);
    Vask::onMemberAdded([MemberHandler::class, 'joined']);
    Vask::onMemberRemoved([MemberHandler::class, 'left']);
    Vask::onClientEvent(LogClientEvent::class); // invokable class
}
```

The route is registered outside the `web` middleware group, so CSRF doesn't apply. Customise the path or take over registration yourself:

```php
Vask::webhookPath('/api/vask-hooks');
Vask::disableAutoWebhookRoute();
```

Full reference: [github.com/vask-dev/laravel](https://github.com/vask-dev/laravel).

## Troubleshooting

### Run `vask:doctor` first

```bash
php artisan vask:doctor
```

It catches missing env vars, bad host config, and broken broadcasts before you go hunting through logs.

### Connection Issues

**Problem:** "Failed to connect to Pusher".

**Solution:** Check your `.env` credentials and ensure `PUSHER_HOST` is set to `wss.vask.dev`. Re-run `php artisan vask:install` to rewrite them.

### Events Not Broadcasting

**Problem:** Events are not being received by clients.

**Solutions:**

- Ensure your queue worker is running: `php artisan queue:work`.
- Check that `BROADCAST_CONNECTION=pusher` in `.env`.
- Verify the event implements `ShouldBroadcast`.
- Check Laravel logs for broadcasting errors.

### Private Channel Authorization Failing

**Problem:** 403 Forbidden when joining private channels.

**Solutions:**

- Verify authorization logic in `routes/channels.php`.
- Ensure user is authenticated before subscribing.
- Check that channel names match between backend and frontend.
- Verify CSRF token is being sent with authorization requests.

### Debug Mode

Enable Pusher debug mode to see detailed logs:

```javascript
window.Echo = new Echo({
    // ... other config
    enabledTransports: ['ws', 'wss'],
    // Enable debug mode
    enableLogging: true,
});

// Also enable Pusher logging
Pusher.logToConsole = true;
```

## Next Steps

- `vask/laravel` package on GitHub: https://github.com/vask-dev/laravel
- `vask/laravel` on Packagist: https://packagist.org/packages/vask/laravel
- [Laravel Broadcasting Docs](https://laravel.com/docs/broadcasting)
- [Laravel Echo on GitHub](https://github.com/laravel/echo)
