PHPackages                             libxa/socket - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. libxa/socket

ActiveLibrary[HTTP &amp; Networking](/categories/http)

libxa/socket
============

A Pusher-protocol WebSocket server for LibxaFrame, built on ReactPHP. Works with existing Pusher clients unchanged.

v0.1.0(2w ago)03↓66.7%MITPHPPHP ^8.3CI failing

Since Jul 18Pushed 2w agoCompare

[ Source](https://github.com/libxa-framework/LibxaSocket)[ Packagist](https://packagist.org/packages/libxa/socket)[ RSS](/packages/libxa-socket/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (1)Dependencies (10)Versions (4)Used By (0)

LibxaSocket
===========

[](#libxasocket)

A WebSocket server for [LibxaFrame](https://github.com/libxa-framework/libxa), speaking the Pusher protocol.

That last part is the point. Rather than inventing a wire format and shipping a client to match, this implements the protocol Pusher defined — so **`pusher-js`and every other Pusher client work against it unchanged**, and so do the server libraries that publish to it.

Built on ReactPHP, the same foundation the reference PHP implementation of this protocol uses: `react/socket` for the event loop and listener, `ratchet/rfc6455` for the handshake and framing.

```
composer require libxa/socket
php libxa package:discover
php libxa socket:install
php libxa socket:start
```

What it does
------------

[](#what-it-does)

- **Public, private and presence channels.** The name prefix is the rule — `private-` and `presence-` require a signature your application vouches for.
- **A presence roster** that counts people rather than connections: one user with two tabs is one member, and closing one tab is not leaving.
- **A signed HTTP API** for publishing, at Pusher's own routes, so `pusher/pusher-php-server` can talk to it.
- **Client events** (`client-*`) relayed browser-to-browser on private and presence channels, for typing indicators and cursors.
- **`broadcast(new SomethingHappened)`** from your application, through a broadcast driver.

Setting up
----------

[](#setting-up)

`socket:install` publishes `config/socket.php`, generates a key and secret into your `.env`, and scaffolds `routes/channels.php`.

Then set the broadcast driver:

```
BROADCAST_DRIVER=socket
```

### Who may listen to what

[](#who-may-listen-to-what)

`routes/channels.php` decides. A private or presence channel with no rule here is **refused** — the alternative allows what nobody has thought about, which makes every channel public until somebody remembers it exists.

```
/** @var \LibxaSocket\Channels\ChannelGate $channel */

// Only the owner may watch their order.
$channel->register('orders.{orderId}', fn ($user, string $orderId): bool =>
    Order::find($orderId)?->user_id === $user->id);

// Presence: return the profile the rest of the room should see.
$channel->register('room.{roomId}', fn ($user, string $roomId): array => [
    'user_id' => (string) $user->id,
    'user_info' => ['name' => $user->name],
]);
```

Register the rule once, without the prefix: `room.{roomId}` covers both `private-room.1` and `presence-room.1`.

Whatever a presence callback returns is visible to **everyone else in the channel**, so it should carry a display name and nothing more.

If your realtime identity is not your login — an anonymous support chat keyed on a session, say — replace the resolver:

```
$channel->resolveUserUsing(fn () => session()?->get('visitor'));
```

Broadcasting
------------

[](#broadcasting)

```
final class OrderShipped implements ShouldBroadcast
{
    public function __construct(public readonly Order $order) {}

    public function broadcastOn(): array
    {
        return ['private-orders.' . $this->order->id];
    }

    public function broadcastWith(): array
    {
        return ['status' => $this->order->status];
    }

    public function broadcastAs(): string
    {
        return 'OrderShipped';
    }
}
```

```
broadcast(new OrderShipped($order));
```

Delivery is best-effort on purpose. A socket server that is down must not take an HTTP request down with it: the order was placed, and the live update not arriving is worth logging rather than a 500. Anything that genuinely cannot lose an event needs a queue.

Connecting a browser
--------------------

[](#connecting-a-browser)

```
npm install @libxa/echo
```

```
import { createEcho } from '@libxa/echo';

const echo = createEcho({ key: import.meta.env.VITE_SOCKET_APP_KEY });

echo.join(`room.${roomId}`)
    .here(users => console.log(users))
    .joining(user => console.log(user.name, 'joined'))
    .leaving(user => console.log(user.name, 'left'))
    .listen('MessagePosted', e => console.log(e.body));
```

[`@libxa/echo`](https://github.com/libxa-framework/libxa-echo) wraps the standard Pusher client with the defaults filled in — several of which are quietly wrong otherwise. The one worth knowing: the client prefixes `App.Events` onto every name given to `.listen()`, while this server publishes `broadcastAs()` unprefixed, so a correct-looking client receives nothing at all, with no error.

You can wire a Pusher client up yourself if you prefer — the protocol is the protocol, and nothing here is LibxaSocket-specific, which is the point of implementing it rather than inventing one. The wrapper exists so that those defaults come from something tested against this server.

Wiring a Pusher client up directly works too. If you do, these are the settings that matter: `cluster: ''`, `disableStats: true`, `enabledTransports: ['ws']`, and `namespace: false` — the last being the one that silently breaks everything if you miss it.

`examples/chat` has a working room — presence, live messages and typing indicators — written against the raw protocol rather than a client library, so every message the wire format involves is visible in one file.

Running it
----------

[](#running-it)

```
php libxa socket:start                 # foreground, Ctrl+C to stop
php libxa socket:start --port=8090     # somewhere else
php libxa socket:start --debug         # log every connection and message
php libxa socket:restart               # ask a running server to stop
```

The server holds every connection in memory and loads your code once at boot, so deploying does not reach it: until it restarts it keeps running the code it started with. `socket:restart` writes a signal the running server watches; it stops cleanly and whatever supervises it — systemd, supervisord, Docker — starts it again. On its own it stops the server and does not start it.

### In front of a browser on HTTPS

[](#in-front-of-a-browser-on-https)

This server speaks `ws://`, not `wss://`. A page served over HTTPS will refuse a `ws://` connection outright, so in production put it behind a reverse proxy that terminates TLS:

```
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_read_timeout 3600s;
}
```

`proxy_read_timeout` matters. The default is 60 seconds, and a WebSocket that is merely quiet looks exactly like one that has stalled.

The HTTP API
------------

[](#the-http-api)

Pusher's routes, signed with Pusher's scheme:

```
POST /apps/{id}/events
GET  /apps/{id}/channels
GET  /apps/{id}/channels/{channel}
GET  /apps/{id}/channels/{channel}/users
GET  /up                                  health, unsigned

```

Everything but `/up` requires a signature. An unauthenticated publish endpoint is a way for anyone who can reach the port to send any message to any of your users.

Security notes
--------------

[](#security-notes)

- **The key is public, the secret is not.** The key ships in your JavaScript and identifies which application a browser is connecting to. The secret signs channel authorizations and the publishing API; anyone holding it can send any message to any of your users.
- **Signatures cover the socket id**, so one minted for a connection cannot be replayed by another.
- **Presence `channel_data` is verified byte-for-byte as sent.** Tampering with it to join as somebody else fails the signature.
- **`pusher:` and `pusher_internal:` event names are reserved** on the publishing API. A forged `member_added` would corrupt every roster listening.
- **Client events are private and presence only.** A public channel anyone can join is one anyone could publish to.

Scaling
-------

[](#scaling)

One process, holding every connection and channel in memory. That is a real limit: two processes do not share channels, so a client connected to one will not receive an event published through the other.

For a single server this is usually fine — ReactPHP handles thousands of connections in one process, and the work per message is small. Beyond that you need a shared backplane, which this does not have yet. The usual answer is Redis pub/sub; it fits here and is the obvious next thing to build.

Requirements
------------

[](#requirements)

PHP 8.3+, and `libxa/framework` ^0.11.2.

License
-------

[](#license)

MIT.

###  Health Score

38

—

LowBetter than 82% of packages

Maintenance96

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Total

3

Last Release

19d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/771945f7f27a8c282d08a644818b7a38d2d972ca0de5b46a10e9092fd8d87278?d=identicon)[voukengdongmofrankysteve](/maintainers/voukengdongmofrankysteve)

---

Top Contributors

[![voukengdongmofrankysteve](https://avatars.githubusercontent.com/u/73015346?v=4)](https://github.com/voukengdongmofrankysteve "voukengdongmofrankysteve (7 commits)")

---

Tags

reactphpwebsocketpusherrealtimeechoBroadcastinglibxalibxaframe

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/libxa-socket/health.svg)

```
[![Health](https://phpackages.com/badges/libxa-socket/health.svg)](https://phpackages.com/packages/libxa-socket)
```

###  Alternatives

[laravel/reverb

Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.

1.6k20.8M132](/packages/laravel-reverb)[ccxt/ccxt

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

43.8k349.0k1](/packages/ccxt-ccxt)[friendsofphp/php-cs-fixer

A tool to automatically fix PHP code style

13.5k263.4M29.2k](/packages/friendsofphp-php-cs-fixer)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

19468.5M2.0k](/packages/drupal-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
