Private
Public Access
194 lines
4.9 KiB
Markdown
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.
|