Files

194 lines
4.9 KiB
Markdown

# Realtime WebSocket Broker
web-app-starter includes an optional WebSocket-to-PHP broker under `server/realtime-broker/`.
The broker is deliberately generic: it owns persistent WebSocket connections, while PHP decides app-specific behavior through a normal HTTP endpoint.
## Files
- `server/realtime-broker/broker.js` - broker daemon and importable `RealtimeBroker` class
- `server/realtime-broker/vendor/ws/` - vendored `ws` 8.14.2, MIT license
- `server/realtime-broker/test-broker.js` - broker integration test
- `lib/realtime.class.php` - PHP helper for backend-initiated commands
- `views/api/realtime.php` - starter demo backend endpoint
- `views/realtime-demo.php` - browser demo page
- `js/u-wsconnection.js` - small browser connection helper
## Start locally
```bash
cd /path/to/web-app-starter
REALTIME_HOST=127.0.0.1 \
REALTIME_PORT=8009 \
REALTIME_BACKEND_URL=http://127.0.0.1/web-app-starter/?api/realtime \
REALTIME_COMMAND_TOKEN=change-me \
node server/realtime-broker/broker.js
```
Then open `/?realtime-demo` and connect.
## Browser URL
In production, proxy `/live/` to the broker and let the browser connect to the same origin:
```nginx
location /live/ {
proxy_pass http://127.0.0.1:8009/live/;
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_read_timeout 3600s;
}
```
If the broker is elsewhere, set `REALTIME_LIVE_URL` or `config/settings.php` `realtime/live_url`.
## PHP backend protocol
For every browser connect/message/disconnect, the broker POSTs URL-encoded fields to `REALTIME_BACKEND_URL`:
- `message`: JSON message
- `connection`: JSON connection/session info
Message types:
- `session-connect`
- `session-disconnect`
- browser messages prefixed as `client-*` unless already prefixed
The PHP endpoint returns a JSON array of commands.
```php
<?php
$message = json_decode($_POST['message'] ?? '{}', true);
$connection = json_decode($_POST['connection'] ?? '{}', true);
print(json_encode([
['type' => 'session', 'data' => ['room' => 'demo']],
['type' => 'send', 'match' => ['room' => 'demo'], 'message' => ['type' => 'notice', 'text' => 'hello']],
]));
```
## Backend commands
### `session`
Merge data into connection session info. Without `match`, applies to the current connection.
```json
{"type":"session","data":{"room":"demo","user_id":123}}
```
### `send`
Send a JSON message to the current connection or matching connections.
```json
{"type":"send","match":{"room":"demo"},"message":{"type":"chat","text":"hello"}}
```
### `close`
Close the current or matching connections.
```json
{"type":"close","match":{"user_id":123}}
```
### `list`
Return matching connection sessions to a command caller.
```json
{"type":"list","match":{"room":"demo"}}
```
### `log`
Log a message if broker logging is enabled.
## PHP helper
`Realtime` wraps command POSTs to the broker:
```php
Realtime::broadcast(['type' => 'notice', 'text' => 'hello'], ['room' => 'demo']);
Realtime::session(['room' => 'demo'], ['user_id' => 123]);
Realtime::connections(['room' => 'demo']);
Realtime::close(['user_id' => 123]);
```
Configure:
```php
$GLOBALS['config']['realtime'] = [
'broker_url' => 'http://127.0.0.1:8009/',
'command_token' => getenv('REALTIME_COMMAND_TOKEN') ?: '',
];
```
## Command security
If `REALTIME_COMMAND_TOKEN` is set, command POSTs must include one of:
- `token=<token>` form field
- `Authorization: Bearer <token>`
- `X-Realtime-Token: <token>`
If no token is set, broker commands are accepted only from `REALTIME_COMMAND_ALLOW` IPs. Use a token for new deployments.
## Broker status
```bash
curl http://127.0.0.1:8009/status
```
Returns JSON counters including active connections, message counts, bytes, backend errors, and denied command attempts.
## systemd example
`/etc/systemd/system/web-app-starter-realtime.service`:
```ini
[Unit]
Description=web-app-starter realtime broker
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/www/web-app-starter
Environment=REALTIME_HOST=127.0.0.1
Environment=REALTIME_PORT=8009
Environment=REALTIME_BACKEND_URL=http://127.0.0.1/web-app-starter/?api/realtime
Environment=REALTIME_COMMAND_TOKEN=change-me
ExecStart=/usr/bin/node server/realtime-broker/broker.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.target
```
## Validation
```bash
node --check server/realtime-broker/broker.js
node --check server/realtime-broker/test-broker.js
node server/realtime-broker/test-broker.js
php -l lib/realtime.class.php
php tests/smoke.php
```
## Notes
- The broker is optional and not started by the PHP app.
- Keep app policy in PHP; keep the broker generic.
- Do not put secrets in messages sent to clients.
- Tune `REALTIME_MAX_MESSAGE_BYTES`, `REALTIME_MAX_BUFFERED_BYTES`, and backend timeout for larger apps.