Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Architecture Websocket
Architecture Websocket
nilsteampassnet/teampass
cursor
websockets
ratchet
reactphp
real-time
auth
rate-limiting
event-broadcasting

Real-time synchronization layer built on Ratchet 0.4.4 (PHP) + ReactPHP event loop. The WebSocket server runs as a separate daemon alongside the web app.

Install
php artisan boost:add-skill nilsteampassnet/teampass

Save this content to: .cursor/rules/architecture-websocket.md

# WebSocket Architecture (feature/websockets)

Real-time synchronization layer built on **Ratchet 0.4.4** (PHP) + **ReactPHP** event loop. The WebSocket server runs as a **separate daemon** alongside the web app.

## Server Structure

```
/websocket/
  bin/server.php              — CLI entry point (daemon, handles SIGTERM/SIGINT)
  src/WebSocketServer.php     — Ratchet MessageComponent (onOpen/onMessage/onClose/onError)
  src/ConnectionManager.php   — Tracks connections + folder subscriptions (SplObjectStorage)
  src/MessageHandler.php      — Routes client actions to handlers
  src/AuthValidator.php       — Validates tokens/sessions on handshake
  src/EventBroadcaster.php    — Polls websocket_events table every 200ms, dispatches
  src/RateLimiter.php         — Sliding window: max 10 msgs/sec per connection
  src/Logger.php              — File-based logger (debug/info/warning/error)
  config/websocket.php        — Server settings
  config/teampass-websocket.service — Systemd unit (user: www-data, restart: always)
```

**Default config** (`/websocket/config/websocket.php`):
- Host: `127.0.0.1` (behind reverse proxy) — Port: `8080`
- Poll interval: 200ms — Batch: 100 events/poll
- Ping: 30s — Pong timeout: 60s — Max connections/user: 5 — Max message: 64KB

**teampass_misc settings:**

| Setting | Default | Purpose |
|---|---|---|
| `websocket_enabled` | `0` | Enable/disable feature |
| `websocket_host` | `127.0.0.1` | Server host |
| `websocket_port` | `8080` | Server port |

## Client Structure

```
/includes/js/teampass-websocket.js       — Core client library (auto-reconnect, pub/sub)
/includes/js/teampass-websocket-init.js  — Event handlers + page integration
```

- **Connection URL**: `ws(s)://hostname/ws` (protocol auto-detected from page)
- **Auto-reconnect**: exponential backoff, max 30s, capped at 10 attempts
- **Heartbeat**: client-side ping every 25s

## Authentication (3 methods, in priority order)

1. **WebSocket token** — 64-char hex, generated by `generateWebSocketToken()` in `main.functions.php:~6638`, stored in `teampass_websocket_tokens`
2. **JWT token** — for API clients
3. **Session cookie** (`PHPSESSID`) — legacy fallback; `key_tempo` validated against DB

## Message Protocol

**Client → Server:**
```json
{ "action": "subscribe", "request_id": "req_xxxxx", "channel": "folder", "data": {"folder_id": 123} }
```

**Server → Client:**
```json
{ "type": "event", "event": "item_updated", "data": {...}, "request_id": "req_xxxxx", "timestamp": 1234567890 }
```

**Client actions:**

| Action | Data | Rate limited |
|---|---|---|
| `subscribe` | `channel`, `folder_id` | Yes |
| `unsubscribe` | `channel`, `folder_id` | Yes |
| `ping` / `pong` | — | No |
| `get_status` | — | Yes |
| `get_stats` | — | Yes (admin only) |
| `renew_item_lock` | `item_id` | Yes |

## Synchronization Events

**Item events** (target: `folder`):

| Event | Triggered by | Key payload fields |
|---|---|---|
| `item_created` | New item saved | `item_id`, `folder_id`, `label`, `created_by` |
| `item_updated` | Item modified | `item_id`, `folder_id`, `label`, `updated_by` |
| `item_deleted` | Item deleted | `item_id`, `folder_id`, `label`, `deleted_by` |
| `item_copied` | Copy operation | `item_id`, `new_item_id`, `folder_id`, `copied_by` |

**Edition lock events** (target: `folder`):

| Event | Triggered by | Key payload fields |
|---|---|---|
| `item_edition_started` | User opens item for edit | `item_id`, `folder_id`, `user_login`, `user_id` |
| `item_edition_stopped` | Lock released or disconnects | `item_id`, `folder_id`, `user_login`, `reason` |

Locks stored in `teampass_items_edition` (`user_id`, `item_id`, `timestamp`). Released automatically on WebSocket disconnect.

**Folder events** (target: `broadcast`): `folder_created`, `folder_updated`, `folder_deleted`, `folder_permission_changed`

**User/task events:**

| Event | Target | Triggered by |
|---|---|---|
| `user_keys_ready` | `user` | Encryption key generation complete |
| `task_progress` | `user` | Long-running task heartbeat |
| `task_completed` | `user` | Background task finished |
| `session_expired` | `user` | Session timeout |
| `system_maintenance` | `broadcast` | Admin trigger |

## PHP Emission Helpers (`sources/main.functions.php`)

```php
// Low-level
emitWebSocketEvent(string $eventType, string $targetType, ?int $targetId, array $payload, ?int $excludeUserId = null): bool
// ~line 6570

// High-level wrappers (use these in sources/*.queries.php)
emitItemEvent(string $action, int $itemId, int $folderId, string $label, string $userLogin, ?int $excludeUserId = null): bool
// action: 'created' | 'updated' | 'deleted'   ~line 6696

emitEditionLockEvent(string $action, int $itemId, int $folderId, string $userLogin, int $userId): bool
// action: 'started' | 'stopped'   ~line 6728

emitFolderEvent(string $action, int $folderId, string $title, string $userLogin, ?int $parentId = null, ?int $excludeUserId = null): bool
// action: 'created' | 'updated' | 'deleted'   ~line 6779

generateWebSocketToken(int $userId): string
// ~line 6638
```

**Rule: always call the high-level helpers** after any write on items/folders. Never insert into `teampass_websocket_events` directly.

## Database Tables

```sql
-- Event queue (polled every 200ms, cleaned after 24h)
teampass_websocket_events:
  id, created_at, event_type VARCHAR(50), target_type ENUM('user','folder','broadcast'),
  target_id INT, payload JSON, processed TINYINT, processed_at

-- Auth tokens (single-use, injected into page)
teampass_websocket_tokens:
  id, user_id, token VARCHAR(64) UNIQUE, created_at, expires_at, used TINYINT

-- Connection log
teampass_websocket_connections:
  id, user_id, resource_id VARCHAR(50), connected_at, disconnected_at, ip_address, user_agent
```

Tables created by `/install/upgrade_run_3.1.7.php`.

## End-to-End Event Flow

```
1. items.queries.php (case 'update_item'):
   DB::update(...) → emitItemEvent('updated', $itemId, $folderId, $label, $userLogin, $excludeId)

2. emitItemEvent() → emitWebSocketEvent('item_updated', 'folder', $folderId, {...})
   → DB::insert(teampass_websocket_events, {processed: 0})

3. EventBroadcaster (daemon, every 200ms):
   → SELECT unprocessed events → broadcastToFolder($folderId, message)
   → UPDATE teampass_websocket_events SET processed=1

4. JS client:
   → tpWs.on('item_updated', handler) fires → UI refreshes
```

## Deployment / Upgrade

**After any change to `app/websocket/src/*`**, the WebSocket daemon must be restarted:

```bash
sudo systemctl restart teampass-websocket.service
```

Without a restart, the running process uses the old code. Clients may send new actions
(e.g., `start_item_view`) that will be silently ignored by the outdated server.

**Hard browser refresh recommended** when the application version number is unchanged between
deployments, because `teampass-websocket-init.js` is loaded with a version query string
(`?v=X.Y.Z`). If the version did not change, browsers may serve a cached copy of the old JS.

WebSocket Architecture (feature/websockets)

Real-time synchronization layer built on Ratchet 0.4.4 (PHP) + ReactPHP event loop. The WebSocket server runs as a separate daemon alongside the web app.

Server Structure

/websocket/
  bin/server.php              — CLI entry point (daemon, handles SIGTERM/SIGINT)
  src/WebSocketServer.php     — Ratchet MessageComponent (onOpen/onMessage/onClose/onError)
  src/ConnectionManager.php   — Tracks connections + folder subscriptions (SplObjectStorage)
  src/MessageHandler.php      — Routes client actions to handlers
  src/AuthValidator.php       — Validates tokens/sessions on handshake
  src/EventBroadcaster.php    — Polls websocket_events table every 200ms, dispatches
  src/RateLimiter.php         — Sliding window: max 10 msgs/sec per connection
  src/Logger.php              — File-based logger (debug/info/warning/error)
  config/websocket.php        — Server settings
  config/teampass-websocket.service — Systemd unit (user: www-data, restart: always)

Default config (/websocket/config/websocket.php):

  • Host: 127.0.0.1 (behind reverse proxy) — Port: 8080
  • Poll interval: 200ms — Batch: 100 events/poll
  • Ping: 30s — Pong timeout: 60s — Max connections/user: 5 — Max message: 64KB

teampass_misc settings:

Setting Default Purpose
websocket_enabled 0 Enable/disable feature
websocket_host 127.0.0.1 Server host
websocket_port 8080 Server port

Client Structure

/includes/js/teampass-websocket.js       — Core client library (auto-reconnect, pub/sub)
/includes/js/teampass-websocket-init.js  — Event handlers + page integration
  • Connection URL: ws(s)://hostname/ws (protocol auto-detected from page)
  • Auto-reconnect: exponential backoff, max 30s, capped at 10 attempts
  • Heartbeat: client-side ping every 25s

Authentication (3 methods, in priority order)

  1. WebSocket token — 64-char hex, generated by generateWebSocketToken() in main.functions.php:~6638, stored in teampass_websocket_tokens
  2. JWT token — for API clients
  3. Session cookie (PHPSESSID) — legacy fallback; key_tempo validated against DB

Message Protocol

Client → Server:

{ "action": "subscribe", "request_id": "req_xxxxx", "channel": "folder", "data": {"folder_id": 123} }

Server → Client:

{ "type": "event", "event": "item_updated", "data": {...}, "request_id": "req_xxxxx", "timestamp": 1234567890 }

Client actions:

Action Data Rate limited
subscribe channel, folder_id Yes
unsubscribe channel, folder_id Yes
ping / pong — No
get_status — Yes
get_stats — Yes (admin only)
renew_item_lock item_id Yes

Synchronization Events

Item events (target: folder):

Event Triggered by Key payload fields
item_created New item saved item_id, folder_id, label, created_by
item_updated Item modified item_id, folder_id, label, updated_by
item_deleted Item deleted item_id, folder_id, label, deleted_by
item_copied Copy operation item_id, new_item_id, folder_id, copied_by

Edition lock events (target: folder):

Event Triggered by Key payload fields
item_edition_started User opens item for edit item_id, folder_id, user_login, user_id
item_edition_stopped Lock released or disconnects item_id, folder_id, user_login, reason

Locks stored in teampass_items_edition (user_id, item_id, timestamp). Released automatically on WebSocket disconnect.

Folder events (target: broadcast): folder_created, folder_updated, folder_deleted, folder_permission_changed

User/task events:

Event Target Triggered by
user_keys_ready user Encryption key generation complete
task_progress user Long-running task heartbeat
task_completed user Background task finished
session_expired user Session timeout
system_maintenance broadcast Admin trigger

PHP Emission Helpers (sources/main.functions.php)

// Low-level
emitWebSocketEvent(string $eventType, string $targetType, ?int $targetId, array $payload, ?int $excludeUserId = null): bool
// ~line 6570

// High-level wrappers (use these in sources/*.queries.php)
emitItemEvent(string $action, int $itemId, int $folderId, string $label, string $userLogin, ?int $excludeUserId = null): bool
// action: 'created' | 'updated' | 'deleted'   ~line 6696

emitEditionLockEvent(string $action, int $itemId, int $folderId, string $userLogin, int $userId): bool
// action: 'started' | 'stopped'   ~line 6728

emitFolderEvent(string $action, int $folderId, string $title, string $userLogin, ?int $parentId = null, ?int $excludeUserId = null): bool
// action: 'created' | 'updated' | 'deleted'   ~line 6779

generateWebSocketToken(int $userId): string
// ~line 6638

Rule: always call the high-level helpers after any write on items/folders. Never insert into teampass_websocket_events directly.

Database Tables

-- Event queue (polled every 200ms, cleaned after 24h)
teampass_websocket_events:
  id, created_at, event_type VARCHAR(50), target_type ENUM('user','folder','broadcast'),
  target_id INT, payload JSON, processed TINYINT, processed_at

-- Auth tokens (single-use, injected into page)
teampass_websocket_tokens:
  id, user_id, token VARCHAR(64) UNIQUE, created_at, expires_at, used TINYINT

-- Connection log
teampass_websocket_connections:
  id, user_id, resource_id VARCHAR(50), connected_at, disconnected_at, ip_address, user_agent

Tables created by /install/upgrade_run_3.1.7.php.

End-to-End Event Flow

1. items.queries.php (case 'update_item'):
   DB::update(...) → emitItemEvent('updated', $itemId, $folderId, $label, $userLogin, $excludeId)

2. emitItemEvent() → emitWebSocketEvent('item_updated', 'folder', $folderId, {...})
   → DB::insert(teampass_websocket_events, {processed: 0})

3. EventBroadcaster (daemon, every 200ms):
   → SELECT unprocessed events → broadcastToFolder($folderId, message)
   → UPDATE teampass_websocket_events SET processed=1

4. JS client:
   → tpWs.on('item_updated', handler) fires → UI refreshes

Deployment / Upgrade

After any change to app/websocket/src/*, the WebSocket daemon must be restarted:

sudo systemctl restart teampass-websocket.service

Without a restart, the running process uses the old code. Clients may send new actions (e.g., start_item_view) that will be silently ignored by the outdated server.

Hard browser refresh recommended when the application version number is unchanged between deployments, because teampass-websocket-init.js is loaded with a version query string (?v=X.Y.Z). If the version did not change, browsers may serve a cached copy of the old JS.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource