Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: openswoole, sqlite3
coverage: none

- name: Validate composer.json and composer.lock
run: composer validate

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
Expand All @@ -34,7 +35,7 @@ jobs:

- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer install --prefer-dist --no-progress --no-suggest
run: composer install --prefer-dist --no-progress

- name: Run test suite
run: composer run-script test
66 changes: 62 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,68 @@

# Socket Conveyor

This package enables you to work with socket messages using routing strategy. For that, you just add an Action Handler implementing the `ActionInterface` to the `SocketMessageRouter` and watch the magic happen!
This package enables PHP 8.2+ applications to work with WebSocket messages
using a routing strategy. Add an action handler implementing `ActionInterface`
to the `SocketMessageRouter`, or run Conveyor in Pusher/Reverb-compatible mode
for Laravel Echo clients.

As an example of how to accomplish that with PHP, you can use the [OpenSwoole](https://openswoole.com/). You can find out more how to use WebSockets with OpenSwoole [here](https://www.youtube.com/watch?v=Vgw5Ibqc15k).
Socket Conveyor is built on [OpenSwoole](https://openswoole.com/). You can
find out more about using WebSockets with OpenSwoole
[here](https://www.youtube.com/watch?v=Vgw5Ibqc15k).

Built for PHP8.2+.
Built for PHP 8.2+.

See more at the [Documentation](https://socketconveyor.com).
## Documentation

- [Complete usage guide](docs/usage.md): installation, native Conveyor mode,
Pusher/Reverb-compatible mode, Laravel Echo setup, HTTP endpoints, smoke
testing, and troubleshooting.
- [Laravel Echo / Reverb compatibility guide](docs/laravel-echo-reverb-compatibility.md):
the shortest path for using Conveyor with Laravel's built-in `reverb` or
`pusher` broadcaster.
- [Real Pusher client smoke example](examples/pusher-real/README.md): local
browser smoke test using `pusher-js` and Laravel Echo.
- [Project documentation site](https://socketconveyor.com).

## Using Conveyor as a Pusher/Reverb server

Conveyor can run in a Pusher-compatible mode for Laravel's stock `pusher` or
`reverb` broadcaster and the standard `pusher-js` / Laravel Echo client.

```php
use Conveyor\Constants;
use Conveyor\ConveyorServer;

(new ConveyorServer())
->port(8080)
->conveyorOptions([
Constants::WEBSOCKET_SUBPROTOCOL => Constants::PUSHER,
Constants::USE_PRESENCE => true,
Constants::APPS => [[
'app_id' => 'local',
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'enable_client_messages' => true,
'enabled' => true,
]],
])
->start();
```

Point Laravel at the Conveyor host and port with the normal Reverb/Pusher env
values:

```dotenv
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=local
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
```

The Pusher mode accepts WebSocket clients at `/app/{key}` and exposes the
signed REST publish API at `/apps/{app_id}/events`,
`/apps/{app_id}/batch_events`, and the channel info endpoints under
`/apps/{app_id}/channels`.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
}
},
"scripts": {
"phpstan": [
"vendor/bin/phpstan analyse --memory-limit=512M"
],
"test": [
"vendor/bin/phpunit --testsuite All"
]
Expand Down
266 changes: 266 additions & 0 deletions docs/laravel-echo-reverb-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# Using Socket Conveyor with Laravel Echo

Socket Conveyor can run as a Pusher/Reverb-compatible server. In that mode,
Laravel applications do **not** need a custom Conveyor broadcaster package for
normal Laravel Echo usage. Use Laravel's built-in `reverb` or `pusher`
broadcast connection and point it at the Conveyor server.

## 1. Start Conveyor in Pusher mode

```php
<?php

use Conveyor\Constants;
use Conveyor\ConveyorServer;

require __DIR__ . '/vendor/autoload.php';

(new ConveyorServer())
->host('127.0.0.1')
->port(8990)
->serverOptions([
'worker_num' => 1,
'task_worker_num' => 1,
])
->conveyorOptions([
Constants::WEBSOCKET_SUBPROTOCOL => Constants::PUSHER,
Constants::USE_PRESENCE => true,
Constants::APPS => [[
'app_id' => 'local-app',
'key' => 'local-key',
'secret' => 'local-secret',
'enable_client_messages' => true,
'enabled' => true,
]],
])
->start();
```

The included real-client smoke server uses the same setup:

```bash
php examples/pusher-real/run-conveyor.php
```

## 2. Configure Laravel

Use Laravel's built-in Reverb-style environment variables and point them at the
Conveyor host and port:

```dotenv
BROADCAST_CONNECTION=reverb

REVERB_APP_ID=local-app
REVERB_APP_KEY=local-key
REVERB_APP_SECRET=local-secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8990
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}"
```

If your app uses the `pusher` connection instead of `reverb`, use equivalent
`PUSHER_*` values with the same app id, key, secret, host, port, and scheme.

## 3. Configure Laravel Echo

Install and use the stock clients:

```bash
npm install laravel-echo pusher-js
```

```js
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,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
enabledTransports: ['ws', 'wss'],
})
```

For private and presence channels, keep the WebSocket host pointed at Conveyor,
but point Echo's authorization endpoint at your Laravel HTTP application:

```dotenv
VITE_LARAVEL_URL=http://localhost:8000
```

```js
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,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
enabledTransports: ['ws', 'wss'],
authEndpoint: `${import.meta.env.VITE_LARAVEL_URL}/broadcasting/auth`,
auth: {
withCredentials: true,
},
})
```

If the browser shows a `403` or posts to your frontend dev server, such as
`http://localhost:8081/broadcasting/auth`, the client did not authorize and is
not subscribed. The auth request must reach Laravel, because Laravel owns
`routes/channels.php` and the authenticated user session.

For Pusher/Reverb mode there is no separate Conveyor access token in the
browser. The authorized connection uses Pusher channel auth:

> Note: In Pusher mode, the browser asks Laravel whether it may enter a
> private or presence channel; Laravel checks the logged-in user and returns a
> signed permission payload that Conveyor verifies with the shared app secret.
> In Conveyor's native protocol, Conveyor itself uses a server token or
> temporary channel token instead of Laravel's normal `/broadcasting/auth`
> Pusher flow.

1. Echo opens the WebSocket to Conveyor at `/app/{key}`.
2. Conveyor returns a `socket_id`.
3. Echo posts `socket_id` and `channel_name` to Laravel's `/broadcasting/auth`.
4. Laravel checks `routes/channels.php` for the authenticated user.
5. Laravel returns an auth signature created with the same app key and secret
configured in Conveyor.
6. Echo sends that signature to Conveyor in `pusher:subscribe`.
7. Conveyor validates the signature before subscribing the socket.

Private channel auth response:

```json
{
"auth": "local-key:computed-hmac-signature"
}
```

Presence channel auth response:

```json
{
"auth": "local-key:computed-hmac-signature",
"channel_data": "{\"user_id\":\"1\",\"user_info\":{\"name\":\"Savio\"}}"
}
```

Laravel creates these responses for you when the built-in `reverb` or `pusher`
broadcaster is configured with the same app id, key, and secret as Conveyor.
Do not put `CONVEYOR_SERVER_TOKEN` in Echo config; that token belongs only to
Conveyor's native protocol.

Then use normal Echo APIs:

```js
window.Echo.channel('orders')
.listen('.OrderShipped', event => {
console.log(event)
})

window.Echo.private('orders.1')
.listen('.OrderUpdated', event => {
console.log(event)
})

window.Echo.join('room.1')
.here(users => console.log('here', users))
.joining(user => console.log('joining', user))
.leaving(user => console.log('leaving', user))
.listenForWhisper('typing', event => console.log('typing', event))
```

## 4. Use Laravel broadcasting normally

Laravel keeps handling `/broadcasting/auth` for private and presence channels.
Your channel authorization callbacks stay in Laravel:

```php
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('orders.{orderId}', function ($user, int $orderId) {
return true;
});

Broadcast::channel('room.{roomId}', function ($user, int $roomId) {
return [
'id' => $user->id,
'name' => $user->name,
];
});
```

In Laravel 13, install broadcasting if your app has not already done so:

```bash
php artisan install:broadcasting
```

Then confirm Laravel registered the auth route:

```bash
php artisan route:list --name=broadcasting
```

Broadcast events normally:

```php
broadcast(new OrderShipped($order))->toOthers();
```

Conveyor receives Laravel's signed Pusher/Reverb REST publish request at
`/apps/{app_id}/events`, delivers the event to connected Echo clients, and
honors `socket_id` exclusion for `toOthers()`.

## 5. Verify with the real browser smoke

Terminal 1:

```bash
php examples/pusher-real/run-conveyor.php
```

Terminal 2:

```bash
php -S 127.0.0.1:8991 examples/pusher-real/router.php
```

Open the Laravel Echo smoke page:

```text
http://127.0.0.1:8991/echo.html
```

Expected results:

- the page reaches `connected` and shows a `socket_id`;
- `Echo.channel('public-demo')`, `Echo.private('demo')`, and
`Echo.join('demo')` subscribe successfully;
- the Public, Private, and Presence buttons trigger REST broadcasts through
Conveyor and log `DemoEvent` in the browser;
- `Public toOthers()` sends the current `socket_id`, so the current tab does
not receive its own broadcast;
- opening a second tab and clicking the whisper button sends a client event to
the other tab.

## When do you need conveyor-laravel-broadcaster?

For Pusher/Reverb-compatible Laravel Echo usage, you should not need a custom
Conveyor Laravel broadcaster. Laravel uses its built-in `reverb` or `pusher`
driver, while Conveyor behaves as the compatible WebSocket and REST broadcast
server.

The older custom broadcaster package is only relevant for applications that
intentionally use Conveyor's native protocol instead of Laravel's Pusher/Reverb
protocol.
Loading
Loading