Monorepo for Aesthetic.Computer aesthetic.computer
at main 214 lines 9.6 kB view raw view rendered
1# Presence System Report 2 3## Current Architecture Overview 4 5There are **TWO separate WebSocket connection systems** that track users: 6 7### 1. Main Session Server WebSocket (port 8889) 8- **File**: `session.mjs` 9- **Connects**: On initial page load, stays connected across ALL pieces 10- **Tracks in `clients[]`**: 11 - `handle` - user's @handle 12 - `user` - auth0 sub ID 13 - `location` - current piece slug (via `location:broadcast` messages) 14 - `ip`, `geo`, etc. 15- **Also tracks `worldClients[piece][id]`**: 16 - Only for pieces with "world" features (like `field`) 17 - Uses `world:*:join`, `world:*:leave`, `world:*:move` messages 18 - Handles ghosting/unghosting when users navigate away and back 19 20### 2. Chat WebSocket (separate connections per instance) 21- **File**: `chat-manager.mjs` 22- **Instances**: `chat-system`, `chat-sotce`, `chat-clock` 23- **Connects**: From `lib/chat.mjs` when a piece calls `chat.connect("system")` 24- **Tracks per instance**: 25 - `instance.connections[id]` - WebSocket connections (ALL connected sockets) 26 - `instance.authorizedConnections[id]` - Only authenticated users who have sent a message 27 - `instance.authorizedConnections[id].handle` - Their @handle 28 29--- 30 31## Why the Online Counter is Inaccurate 32 33### Problem 1: Chat connects globally, not per-piece 34The chat connection is established from `lib/chat.mjs` and **persists across piece navigation**. When you're on `aesthetic.computer/prompt` or any other piece, you're still connected to chat-system. The `chatterCount` in chat shows ALL connected WebSockets, not just users actually viewing the `chat` piece. 35 36### Problem 2: `onlineHandles` only shows AUTHORIZED users 37The `getOnlineHandles()` function in chat-manager.mjs: 38```javascript 39getOnlineHandles(instance) { 40 const handles = []; 41 for (const [id, auth] of Object.entries(instance.authorizedConnections)) { 42 if (auth.handle && instance.connections[id]) { 43 handles.push(auth.handle); 44 } 45 } 46 return [...new Set(handles)]; 47} 48``` 49This only includes users who: 501. Have an open WebSocket connection 512. Have successfully authorized (sent a chat message with valid token) 523. Have a handle stored 53 54**Anonymous viewers or logged-in users who haven't chatted** are NOT in `onlineHandles`. 55 56### Problem 3: Connection count includes duplicates 57`Object.keys(instance.connections).length` counts WebSocket connections, not unique users. One user with multiple tabs = multiple connections. 58 59--- 60 61## What We Know About User Location 62 63The **main session server** (`session.mjs`) DOES track which piece users are on: 64 65```javascript 66// From location:broadcast handler (line 1708-1750) 67if (msg.content.slug !== "*keep-alive*") { 68 clients[id].location = msg.content.slug; 69 log(`📍 Location updated for ${clients[id].handle || id}: "${msg.content.slug}"`); 70} 71``` 72 73This is published to Redis: 74```javascript 75pub.publish("slug:" + msg.content.handle, msg.content.slug) 76``` 77 78**Key insight**: The session server knows exactly which piece each user is on! 79 80--- 81 82## Proposed Solution: "Here" Counter for Chat Piece 83 84### Option A: Cross-reference session server location data 85 86The chat-manager could query the session server's `clients[]` to see which authenticated chat users have `location === "chat"`. 87 88**Implementation**: 891. Export a function from session.mjs: `getClientsOnPiece(piece)` 902. In chat-manager, after broadcasting `online-handles`, also send `here-handles` 913. `here-handles` = intersection of `onlineHandles` AND users where `clients[id].location === "chat"` 92 93### Option B: Chat piece explicitly notifies "in-view" 94 95When the `chat` piece mounts, it could send a message like `chat:enter`. When unmounting/leaving, `chat:leave`. 96 97**Pros**: Explicit, accurate 98**Cons**: Requires piece-level code changes, needs cleanup on disconnect 99 100### Option C: Track "last seen piece" per chat connection 101 102Map chat connection IDs to session server connection IDs (via handle or token), then look up their `location`. 103 104--- 105 106## Data Flow Diagram 107 108``` 109┌──────────────────────────────────────────────────────────────┐ 110│ BROWSER │ 111│ ┌─────────────────────────────────────────────────────────┐ │ 112│ │ BIOS/Disk │ │ 113│ │ - Connects to session-server:8889 on load │ │ 114│ │ - Sends location:broadcast when piece changes │ │ 115│ │ - Stays connected across ALL pieces │ │ 116│ └─────────────────────────────────────────────────────────┘ │ 117│ │ │ 118│ ┌───────────────────────────┼──────────────────────────────┐│ 119│ │ lib/chat.mjs │ ││ 120│ │ - Called by chat piece │ ││ 121│ │ - Connects to chat-*.aesthetic.computer ││ 122│ │ - Also persists across piece navigation! ││ 123│ └───────────────────────────┼──────────────────────────────┘│ 124└──────────────────────────────┼───────────────────────────────┘ 125126 ┌─────────────────────┴─────────────────────┐ 127 ▼ ▼ 128┌─────────────────────┐ ┌─────────────────────┐ 129│ session.mjs:8889 │ │ chat-manager.mjs │ 130│ │ │ (chat-*.aesthetic) │ 131├─────────────────────┤ ├─────────────────────┤ 132│ clients[id] = { │ │ connections[id] = │ 133│ handle, │ │ WebSocket │ 134│ user, │ ← NO LINK → │ │ 135│ location: "chat" │ │ authorizedConns = │ 136│ } │ │ { token, handle } │ 137├─────────────────────┤ ├─────────────────────┤ 138│ KNOWS: who's on │ │ KNOWS: who's │ 139│ "chat" piece │ │ connected │ 140│ │ │ to chat WS │ 141│ DOESN'T KNOW: │ │ │ 142│ who's auth'd for │ │ DOESN'T KNOW: │ 143│ chat specifically │ │ actual piece │ 144└─────────────────────┘ └─────────────────────┘ 145``` 146 147--- 148 149## Recommended Implementation 150 151### Phase 1: Bridge the data (quick win) 152 153In `session.mjs`, add a function: 154```javascript 155function getHandlesOnPiece(pieceName) { 156 return Object.values(clients) 157 .filter(c => c.location === pieceName && c.handle) 158 .map(c => c.handle); 159} 160``` 161 162Export this so chat-manager can call it, or expose via internal API. 163 164### Phase 2: Broadcast "here" in chat 165 166Modify `broadcastOnlineHandles()` in chat-manager: 167```javascript 168broadcastOnlineHandles(instance) { 169 const handles = this.getOnlineHandles(instance); 170 const hereHandles = this.getHereHandles(instance); // Users actually on chat piece 171 this.broadcast(instance, this.pack("presence", { 172 online: handles, // All auth'd chat connections 173 here: hereHandles // Only those on "chat" piece right now 174 })); 175} 176``` 177 178### Phase 3: UI in chat.mjs 179 180```javascript 181// Currently cycles through onlineHandles 182// Add a "Here" section that shows users actually viewing chat 183const hereHandles = client?.hereHandles || []; 184const onlineHandles = client?.onlineHandles || []; 185``` 186 187--- 188 189## Questions to Resolve 190 1911. **Should chat disconnect when leaving the chat piece?** 192 - Currently it stays connected (enables notifications anywhere) 193 - Could add explicit `chat:enter`/`chat:leave` events instead 194 1952. **How to link session ID to chat ID?** 196 - Both have `handle` - match on that? 197 - Or pass session ID in chat auth? 198 1993. **Should "here" count anonymous viewers?** 200 - Session server tracks location for everyone 201 - Chat only tracks authorized users 202 203--- 204 205## Files to Modify 206 2071. `session-server/session.mjs` - Export helper function or add internal API 2082. `session-server/chat-manager.mjs` - Add "here" calculation and broadcast 2093. `system/public/aesthetic.computer/lib/chat.mjs` - Handle new `presence` message type 2104. `system/public/aesthetic.computer/disks/chat.mjs` - Display "here" users 211 212--- 213 214*Report generated: 2026-01-17*