Home Doh Ref
Dohballs
  • 📁 doh_chat
    • 📁 chat_extensions
  • 📁 doh_modules
    • 📦 dataforge
    • 📦 express
    • 📁 sso
    • 📁 user

Chat

Core room-based chat system with DMs, presence tracking, AI bots, and real-time collaboration.

Overview

The chat module is the foundation of the doh_chat suite. It provides a full-featured chat application with public/private rooms, direct messages, real-time presence, file sharing, collaborative documents, AI bot conversations, and video/audio calls. The server handles all persistence (SQLite via AsyncDataforge), permission enforcement, and socket event routing. The client renders a composable UI with sidebar navigation, message lists, settings panels, and embedded content tiles.

Architecture

chat.doh.yaml
├── chat_url_utils          (shared)
├── ai_services             (nodejs)
├── chat_server             (nodejs)   ← routes, socket events, permissions, DB
├── chat_files_server       (nodejs)
├── chat_push_server        (nodejs)
├── chat_calls_server       (nodejs)
├── collab_docs             (shared)
├── collab_richtext         (shared)
├── user_avatar             (browser)
├── chat_client             (browser)  ← full UI (ChatApp + 40 patterns)
├── chat_files_client       (browser)
├── chat_push_client        (browser)
└── chat_calls_client       (browser)

Depends on: express_router, user_host (auth), db_dataforge Depended on by: chat_embed, issues

Features

  • Public lobby room (always available, auto-join)
  • Room-based chat with permission-gated creation
  • Direct messages between users
  • AI bot conversations with multi-provider support
  • Real-time presence tracking (online/offline indicators)
  • Message history with edit/delete and provenance tracking
  • Typing indicators
  • File attachments (via chat_files)
  • WebRTC audio/video calls (via chat_calls)
  • Collaborative code documents (via collab_docs)
  • Collaborative rich text documents (via collab_richtext)
  • Push notifications for iOS (via chat_push)
  • Browser extension tool integration
  • Contact/connection request system with blocking
  • Sidebar folder organization with drag-and-drop
  • Email invitations
  • Server log streaming for admins
  • YouTube video synchronization
  • Scroll position synchronization
  • Admin maintenance tools (delete rooms, token usage)

File Structure

File Purpose
chat.doh.yaml Outer Package definition
chat_server.js Server routes, socket events, permissions, DB setup (~5200 lines)
chat_client.js Full client UI — 40 Doh Patterns (~12,400 lines)
chat_calls_server.js WebRTC call signaling server (~400 lines)
chat_calls_client.js Call manager and call UI patterns (~970 lines)
chat_push_server.js Push notification registration and sending (~385 lines)
chat_push_client.js Capacitor push notification setup (~200 lines)
chat_url_utils.js URL parsing utilities (~100 lines)
chat.css Main chat styles
chat_calls.css Call UI styles
BROWSER_TOOLS.md Browser extension tool registry docs

Configuration

# pod.yaml
chat:
  lobby_name: "General Chat"
  max_message_length: 5000
  attachment_storage_enabled: true
  user_key_secret: "your-secret-key"  # AES key for encrypting user AI keys
  models: {}                          # AI model configuration overrides
  providers: {}                       # Provider key map overrides

Database Schema

All tables are in the chat database (SQLite via AsyncDataforge). All are Idea tables (id TEXT, data TEXT JSON blob) unless noted.

Table Purpose
chat.rooms Room metadata (name, type, members, settings, created_by)
chat.messages Message history (sender, content, timestamps, provenance)
chat.room_members Membership records (username, role, joined_at, unread_count)
chat.contacts User contact lists
chat.connections Established user-to-user connections
chat.connection_requests Pending connection requests
chat.blocked_users Blocked user records
chat.email_invites Email-based room invitations
chat.sidebar_layouts Saved sidebar panel layouts per user
chat.trace Browser navigation trace/history entries
chat.bots Bot configurations and metadata
chat.user_ai_keys Encrypted user API keys (AES-256-GCM)
chat.pro_interest Doh Pro subscription interest registrations
chat.token_usage AI token usage tracking per user/room/model

Constants: LOBBY_ID = 'lobby'

Permissions

Contexts

Context Condition
chat_room ctx?.roomId exists
server_logs Always true
chat_maintenance Always true
dohpro Always true

Groups

Group Type Permissions Use Case
chat_room_creator Assignable create:chat_room Users who can create rooms
log_viewer Assignable view:server_logs Users who can stream server logs
chat_admin Assignable admin:chat_maintenance Admin maintenance (delete rooms, view all token usage)
dohpro_subscriber Assignable is:dohpro Doh Pro subscribers (access managed AI keys)

Granting Permissions

doh users add-group user@example.com chat_room_creator
doh users add-group user@example.com chat_admin

REST API Reference

All routes are prefixed with /api/chat/ unless noted. Auth = [Users.requireAuth]. Nickname = user must have set chat_name.

Profile & Settings

Method Path Auth Description
POST /api/chat/nickname Yes Set chat display name
GET/POST /api/chat/avatar Yes Get/set avatar preferences
GET /api/chat/me Yes Get current user profile

POST /api/chat/nickname

// Request
{ chat_name: string }  // 2-32 chars
// Response
{ success: true, chat_name, user: {...} }
// Errors: 400 (length), 409 (duplicate)

POST /api/chat/avatar

// Request
{ avatar_salt?: number, avatar_style?: string }
// Styles: 'initials', 'bottts-neutral', 'pixel-art', 'identicon', 'rings', 'shapes'
// Response
{ success: true, avatar_salt, avatar_style }

Rooms — Discovery

Method Path Auth Description
GET /api/chat/lobby Yes + Nick Get lobby room and members
GET /api/chat/rooms/public Yes + Nick List all public rooms
GET /api/chat/rooms Yes + Nick List rooms user is a member of
GET /api/chat/rooms/:id Yes + Nick Get single room details + members
GET /api/chat/rooms/:id/messages Optional Fetch message history (paginated)

GET /api/chat/rooms/:id/messages

// Request (query/body)
{ limit?: number, skip?: number }
// Response
{ messages: [{ id, sender, sender_name, content, created_at, edited_at, deleted, provenance, ... }] }

Rooms — Management

Method Path Auth Permission Description
POST /api/chat/rooms/create Yes + Nick create:chat_room Create a room
PATCH /api/chat/rooms/:id Yes Room admin Edit room settings
DELETE /api/chat/rooms/:id Yes Room admin Delete room
POST /api/chat/rooms/:id/delete Yes Room creator Delete room (explicit)

POST /api/chat/rooms/create

// Request
{ name: string, members?: string[], type?: 'public'|'private'|'dm'|'bot'|'issue', is_public?: boolean }
// Response
{ room: { id, name, type, members, created_by, created_at, ... } }
// Broadcast: chat:room_updated to all members

Room Membership

Method Path Auth Role Description
POST /api/chat/rooms/:id/join Yes + Nick -- Join public/auto_membership room
POST /api/chat/rooms/:id/leave Yes -- Leave room (can't leave lobby)
POST /api/chat/rooms/:id/members Yes Admin Add member
PATCH /api/chat/rooms/:id/members/:username Yes Admin Change member role
DELETE /api/chat/rooms/:id/members/:username Yes Admin Remove member
GET /api/chat/rooms/:id/members Yes -- List members

POST /api/chat/rooms/:id/members

// Request
{ username: string, role?: 'member'|'admin' }
// Response
{ success: true, member: { username, role, joined_at, ... } }
// Broadcast: chat:member_added

Direct Messages

Method Path Auth Description
POST /api/chat/dm/:username Yes + Nick Create/get DM room with user
POST /api/chat/dm/bot/:bot_id Yes Create/get DM room with bot

POST /api/chat/dm/:username

// Response
{ room: { id, type: 'dm', members: [user, target], partner_chat_name, ... } }
// Broadcast (if new): chat:room_created to both users

AI & Bots

Method Path Auth Description
GET /api/chat/ai/models Yes List available AI models (filtered by keys/pro)
GET /api/chat/ai/keys Yes Get user's key status per provider (masked)
POST /api/chat/ai/keys/save Yes Save encrypted AI key
POST /api/chat/ai/keys/delete Yes Delete AI key
POST /api/chat/ai/keys/reveal Yes Decrypt and return full key
GET /api/chat/bots Yes List user's bots
POST /api/chat/bots/create Yes Create a bot
GET /api/chat/bots/:id Yes Get bot details
POST /api/chat/bots/:id/update Yes Update bot config
POST /api/chat/bots/:id/delete Yes Delete bot

GET /api/chat/ai/models

// Response
{
  models: [{
    nickname: string, name: string, description: string, provider: string,
    accessible: boolean, access_source: 'user_key'|'doh_pro'|null,
    reason: 'no_key'|null
  }],
  is_doh_pro: boolean
}

Bot Room Operations

Method Path Auth Description
POST /api/chat/rooms/:id/clear-context Yes Clear bot conversation context
POST /api/chat/rooms/:id/compress Yes Summarize and compress context
POST /api/chat/rooms/:id/clone Yes Clone bot room with empty history
POST /api/chat/rooms/:id/model Yes Change bot's AI model

Token Usage

Method Path Auth Description
GET /api/chat/token-usage Yes User's aggregated token usage
GET /api/chat/rooms/:id/token-usage Yes Per-room token usage
GET /api/chat/admin/token-usage Admin All users' token usage

GET /api/chat/token-usage

// Response
{
  models: { "model_name": { input_tokens, output_tokens, cost, calls } },
  total: { input_tokens, output_tokens, cost, calls }
}

Contacts & Connections

Method Path Auth Description
GET /api/chat/contacts Yes Get contact list with online status
POST /api/chat/contacts/:username Yes Add to contacts
DELETE /api/chat/contacts/:username Yes Remove from contacts
POST /api/chat/connections/request/:username Yes Send connection request
GET /api/chat/connections/requests/pending Yes Incoming requests
GET /api/chat/connections/requests/sent Yes Outgoing requests
POST /api/chat/connections/accept/:requester Yes Accept request
POST /api/chat/connections/decline/:requester Yes Decline request
GET /api/chat/connections Yes All connections
GET /api/chat/connections/status/:username Yes Check status with user
DELETE /api/chat/connections/remove/:username Yes Remove connection

Blocking

Method Path Auth Description
POST /api/chat/block/:username Yes Block user
POST /api/chat/unblock/:username Yes Unblock user
GET /api/chat/blocked Yes List blocked users

Presence & Users

Method Path Auth Description
GET /api/chat/presence Yes + Nick Online users list
GET /api/chat/users Yes + Nick Paginated user discovery
GET /api/chat/users/by-email Yes Find user by email

Sidebar & Links

Method Path Auth Description
GET /api/chat/sidebar-layout Yes Get saved sidebar layout
POST /api/chat/sidebar-layout/save Yes Save sidebar layout
POST /api/chat/sidebar-layout/reset Yes Reset to default
GET /api/chat/links/user Yes + Nick Get browser trace links

Invites & Pro

Method Path Auth Description
POST /api/chat/invite-by-email Yes Send room invite via email
POST /api/chat/ai/pro-interest Yes Register for Doh Pro
GET /api/chat/ai/pro-interest/check Yes Check Pro registration

Admin

Method Path Auth Permission Description
POST /api/chat/admin/delete-all-rooms Yes admin:chat_maintenance Delete all rooms except lobby
POST /api/chat/admin/delete-empty-rooms Yes admin:chat_maintenance Delete rooms with < 3 messages
// Response (both)
{ success: true, deleted_count: number }

Page Route

Method Path Auth Description
GET /chat Yes Full chat application page

Socket Events Reference

All events use wrapSocketHandler for consistent error handling. Auth comes from socket.user (authenticated) or socket.guestUser (guest with nickname).

Connection & Identity

Event Direction Payload Response/Broadcast
chat:set_guest_identity Client → Server { chat_name, guest_id? } cb({ success, guest_user })

Room Lifecycle

Event Direction Payload Response/Broadcast
chat:join Client → Server { room_id } cb({ success })
chat:leave Client → Server { room_id } cb({ success })
chat:user_joined Server → Room { username, chat_name, online } Broadcast to room
chat:room_peer_joined Server → Room { username, chat_name } Broadcast to room

Messaging

Event Direction Payload Response/Broadcast
chat:message Client → Server { room_id, content, scroll_y?, scroll_percent?, page_url? } cb({ success, message })
chat:edit Client → Server { message_id, content } cb({ success })
chat:delete Client → Server { message_id } cb({ success })
chat:notey_edit Client → Server { message_id, content } cb({ success })
chat:read Client → Server { room_id, message_id? } cb({ success })
chat:reorder Client → Server { room_id, ordering: string[] } cb({ success })
chat:message_received Server → Room { room_id, message } Broadcast
chat:message_edited Server → Room { room_id, message_id, content, edited_at } Broadcast
chat:message_deleted Server → Room { room_id, message_id } Broadcast
chat:unread_update Server → User { room_id, unread_count } Per-user
chat:messages_reordered Server → Room { room_id, ordering } Broadcast

Message object shape (returned in chat:message callback and chat:message_received):

{
  id: string,                  // UUID
  room_id: string,
  sender: string,              // username
  sender_name: string,         // chat_name
  avatar_salt: number,
  avatar_style: string,
  content: string,
  created_at: number,          // timestamp
  edited_at: number | null,
  deleted: boolean,
  is_guest: boolean,
  original_sequence_number: number,
  adhoc_sequence_number: number | null,
  provenance: [{ action: 'created'|'edited'|'deleted', user, name, at }]
}

Typing

Event Direction Payload Response/Broadcast
chat:typing Client → Server { room_id, typing: boolean } cb({ success })
chat:user_typing Server → Room { room_id, username, chat_name, typing } Broadcast (excludes sender)

Presence

Event Direction Payload Broadcast
chat:presence Server → Lobby { username, chat_name, online } All lobby users

Embedded Documents

Event Direction Payload Response/Broadcast
chat:update_doc_size Client → Server { message_id, doc_id, width, height, isLive? } cb({ success })
chat:update_doc_open_state Client → Server { message_id, doc_id, isOpen } cb({ success })
chat:doc_size_updated Server → Room { message_id, doc_id, width, height } Broadcast
chat:doc_open_state_updated Server → Room { message_id, doc_id, isOpen } Broadcast

Browser Panels

Event Direction Payload Response/Broadcast
chat:browser_panel:open Client → Server { room_id, panel_id, url, left, top, width, height, owner } cb({ success, panel_id })
chat:browser_panel:url Client → Server { room_id, panel_id, url } cb({ success })
chat:browser_panel:state Client → Server { room_id, panel_id, left, top, width, height } cb({ success })
chat:browser_panel:close Client → Server { room_id, panel_id } cb({ success })
chat:browser_panel:opened Server → Room Same as open payload Broadcast (excludes sender)
chat:browser_panel:url_update Server → Room { panel_id, url } Broadcast (excludes sender)
chat:browser_panel:state_update Server → Room { panel_id, left, top, width, height } Broadcast (excludes sender)

Browser Tool Integration

Event Direction Payload
chat:browser_tool_request Server → Room { room_id, request_id, tool_name, params, timeout }
chat:browser_tool_result Client → Server { room_id, request_id, result }

See BROWSER_TOOLS.md for available tools.

Trace / Browser History

Event Direction Payload Response/Broadcast
chat:trace_update Client → Server { room_id, url, title, favicon_domain?, scroll_anchor? } cb({ success })
chat:trace_sync Client → Server { room_id } cb({ entries: [...] })
chat:trace_delete Client → Server { room_id, entry_id } cb({ success })
chat:trace_rename Client → Server { room_id, entry_id, nickname, url? } cb({ success })
chat:trace_updated Server → Room { room_id, entry } Broadcast
chat:trace_deleted Server → Room { room_id, entry_id } Broadcast
chat:trace_renamed Server → Room { room_id, entry_id, nickname, url? } Broadcast

Scroll Sync

Event Direction Payload
chat:scroll_sync Client → Server { room_id, payload: { scroll_y, scroll_percent, ... } }
chat:scroll_sync Server → Room { room_id, from_user, payload } (excludes sender)

YouTube Sync

Event Direction Payload
chat:yt_sync Client → Server { room_id, message_id, url, action: 'play'|'pause'|'seek', time }
chat:yt_sync Server → Room Same payload (excludes sender)

Server Logs

Event Direction Payload Notes
chat:logs:subscribe Client → Server { filter? } Requires view:server_logs
chat:logs:unsubscribe Client → Server --
chat:logs:entry Server → Client { timestamp, level, message, source } Stream

Calls (via chat_calls_server.js)

Event Direction Payload
chat:call:join Client → Server { room_id, audio: bool, video: bool }
chat:call:leave Client → Server { room_id }
chat:call:toggle_audio Client → Server { room_id, enabled }
chat:call:toggle_video Client → Server { room_id, enabled }
chat:call:toggle_screen Client → Server { room_id, enabled }
chat:call:rtc_offer Client → Server { room_id, target, offer }
chat:call:rtc_answer Client → Server { room_id, target, answer }
chat:call:rtc_ice Client → Server { room_id, target, candidate }
chat:call:get_state Client → Server { room_id }
chat:call:state Server → Room { room_id, participants: [...] }
chat:call:peer_joined Server → Room { room_id, username, audio, video }
chat:call:peer_left Server → Room { room_id, username }
chat:call:media_state Server → Room { room_id, username, audio, video, screen }

Push Notifications (via chat_push_server.js)

Event Direction Payload
chat:push:register Client → Server { token, platform }
chat:push:unregister Client → Server { token }

Room/Connection Broadcasts

Event Payload Notes
chat:room_created { room } Sent to new members
chat:room_updated { room } Sent to room members
chat:room_deleted { room_id } Sent to room members
chat:member_added { room_id, member } Sent to room
chat:member_removed { room_id, username } Sent to room
chat:context_cleared { room_id } Sent to room
chat:sidebar_layout_updated { layout } Sent to user's sockets
chat:connection_request { from_username, from_chat_name } Sent to target
chat:connection_accepted { username } Sent to both users
chat:connection_removed { username } Sent to both users

Programmatic API

chat_server exports a Chat object for use by other modules:

Doh.Module('my_module', ['chat_server'], function(Chat) {
  // Create a system room
  const room = await Chat.createSystemRoom({
    name: 'Issue #42',
    type: 'issue',
    members: ['alice', 'bob'],
    adminUser: 'alice'
  });

  // Manage membership
  await Chat.addMemberToRoom(room.id, 'charlie', 'member');
  await Chat.removeMemberFromRoom(room.id, 'bob');

  // Query state
  const online = Chat.isUserOnline('alice');           // boolean
  const rooms = Chat.getUserRooms('alice');             // Room[]
  const msgs = Chat.getRecentMessages(room.id, 50);    // Message[]
  const isMember = Chat.isRoomMember(room.id, 'alice'); // boolean
  await Chat.deleteRoom(room.id);
});

Exported Functions

Function Params Returns Description
createSystemRoom(config) { name, type?, members?, created_by?, is_public?, allow_guests?, adminUser? } Room Create room programmatically
addMemberToRoom(roomId, username, role?) string, string, 'member'|'admin' MemberRecord | null Add member
removeMemberFromRoom(roomId, username) string, string void Remove member
deleteRoom(roomId) string boolean Delete room and all data
isUserOnline(username) string boolean Check presence
trackUserOnline(socket, user) socket, user void Register presence
trackUserOffline(socket) socket void Unregister presence
getOnlineUsers() -- [{ username, chat_name }] All online users
broadcastUserPresence(user, online) user, boolean void Emit presence event
getRoomById(roomId) string Room | null Fetch room
getUserRooms(username) string Room[] User's rooms
getPublicRooms() -- Room[] All public rooms
getRecentMessages(roomId, limit?) string, number Message[] Fetch messages
isRoomMember(roomId, username) string, string boolean Check membership
canParticipate(user) user boolean Has chat_name set

Client Patterns

Core UI (chat_client.js)

Pattern Parent Purpose
ChatApp ChatConversation Top-level app: sidebar, room state, nickname setup
ChatConversation html Abstract base: messages, input, controls, socket wiring
ChatSidebar html Left nav: rooms, DMs, folders, issues section, user panel
ChatMessageList html Message container with layout modes (linear/thread/grouped)
ChatMessage html Single message: avatar, content, actions, attachments
ChatInput html Textarea with emoji picker, file attach, link preview, auto-resize
ChatTypingIndicator html "User is typing..." display
ChatNicknameSetup html First-login nickname prompt

Content Tiles

Pattern Parent Purpose
ChatCollabTile html Embedded collab document tile with mini editor
ChatLogTile html Log/trace content display (URLs, stack traces)
ChatUrlTile html Unfurled URL preview (title, description, thumbnail)
ChatUrlOverlay html Full-page URL preview expansion
ChatDropPopover html File drag-and-drop preview popover

Modals & Menus

Pattern Parent Purpose
ChatAddMenu html Dropdown: create room, contact, folder, doc, bot
ChatModal html Base modal dialog container
ChatEmojiPickerModal html Emoji picker modal
ChatAddContactModal html Add contact by username
ChatUserPickerModal html Multi-select user picker
ChatFolderContextMenu html Folder right-click: rename, delete
ChatRoomContextMenu html Room right-click: rename, delete, archive

Settings Panels

Pattern Parent Purpose
ChatSettings html Root settings container with tab nav
ChatSettingsAccount html Profile, password, preferences
ChatSettingsAppearance html Theme, font size, message layout
ChatSettingsExtensions html Plugins management
ChatSettingsAIKeys html AI provider API key management
ChatSettingsUsage html Token usage analytics
ChatSettingsAIBots html Bot definitions and config
ChatSettingsNotifications html Sound, desktop, mute rules
ChatSettingsAttachments html File upload limits
ChatSettingsStorage html Local cache, export/import
ChatSettingsCollab html Collab document settings
ChatSettingsLinks html URL unfurling preferences
ChatSettingsConnections html Integrations management
ChatSettingsMaintenance html Admin tools (delete rooms, diagnostics)

Sidebar Items

Pattern Parent Purpose
ChatSidebarNode html Base tree node (draggable, selectable)
ChatRoomItem html Room in sidebar with unread badge
ChatFolderItem html Folder with expand/collapse
ChatOnlineAvatars html Online user avatar row
ChatMiniProfile html User hover card

Utility

Pattern Parent Purpose
ChatToast html Temporary notification
ChatEmojiPicker html Floating emoji picker

Call UI (chat_calls_client.js)

Pattern/Object Parent Purpose
ChatCallManager object WebRTC peer connections, media tracks, call state
ChatCallIndicator html Room header badge: active call, join buttons
ChatCallBar html Controls during call: mute, video, screen share, leave, grid
ChatCallRoomBadge html Room sidebar badge showing participant count

Key Design Patterns

Effective User Resolution

Socket handlers resolve user via: socket.user (authenticated) → socket.guestUser (guest) → null

Presence Tracking

onlineUsers Map: username → { sockets: Set<socket.id>, chat_name }
socketToUser Map: socket.id → username
userRooms Map: socket.id → Set<room_id>

Message Provenance

Every message tracks its edit/delete history:

provenance: [
  { action: 'created', user: 'alice', name: 'Alice', at: 1700000000 },
  { action: 'edited', user: 'alice', name: 'Alice', at: 1700000060 }
]

AI Key Encryption

User API keys are encrypted with AES-256-GCM. Stored as "iv:tag:encrypted" (hex, colon-separated). Masked display shows first 7 + last 4 characters.

Testing

  1. Start server and open /chat
  2. Verify: lobby loads, room list populates, nickname prompt appears on first visit
  3. Verify messaging: send, edit, delete, typing indicator
  4. Verify DMs: create DM, send messages, check unread badges
  5. Verify calls: join call, audio/video toggle, screen share
  6. Verify socket flows: trace updates, browser panel sync in multiple clients
  7. Verify admin: delete empty rooms (requires chat_admin group)
Last updated: 2/21/2026