mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Server:
- Split monolithic voice_handlers.go into voice_join/leave/controls/broadcast
- Add metrics endpoint (admin-IP-restricted /api/v1/metrics)
- Add orphaned attachment cleanup in maintenance loop
- Add sentinel errors (db/errors.go, ws/errors.go)
- Add ring buffer for event replay on reconnect
- Add heartbeat monitoring with stale connection sweep
- Improve hub with panic recovery, graceful shutdown, seq tracking
- Typed message structs replace raw map[string]interface{}
Client:
- Decompose MainPage into ChatArea + SidebarArea controllers
- Add disposable.ts lifecycle management pattern
- Add member list right-click context menu (kick/ban/role)
- Tighten CSP (media-src, font-src, object-src, base-uri)
- Improve store with shallowEqual, 500-msg cap, batch updates
- Add search API endpoint wiring
- Fix LiveKit session cleanup and reconnection
Docs:
- Add CODEMAPS for architecture, backend, frontend, data, deps
- Add protocol-schema.json (machine-readable, 36 message types)
- Add platform research report
- Update PROTOCOL.md with seq/replay fields
350 lines
8.4 KiB
JSON
350 lines
8.4 KiB
JSON
{
|
|
"$schema": "protocol-schema",
|
|
"version": "1.0",
|
|
"description": "OwnCord WebSocket protocol schema. Single source of truth for Server (Go) and Client (TypeScript) message types.",
|
|
"envelope": {
|
|
"type": "string",
|
|
"id": "string|undefined",
|
|
"payload": "object"
|
|
},
|
|
"messages": {
|
|
"auth_ok": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user": {
|
|
"id": "number",
|
|
"username": "string",
|
|
"avatar": "string|null",
|
|
"role": "string"
|
|
},
|
|
"server_name": "string",
|
|
"motd": "string"
|
|
}
|
|
},
|
|
"auth_error": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"message": "string"
|
|
}
|
|
},
|
|
"ready": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channels": "array<ReadyChannel>",
|
|
"members": "array<ReadyMember>",
|
|
"voice_states": "array<ReadyVoiceState>",
|
|
"roles": "array<ReadyRole>"
|
|
},
|
|
"nested_types": {
|
|
"ReadyChannel": {
|
|
"id": "number",
|
|
"name": "string",
|
|
"type": "string",
|
|
"category": "string|null",
|
|
"position": "number",
|
|
"unread_count": "number|undefined",
|
|
"last_message_id": "number|undefined"
|
|
},
|
|
"ReadyMember": {
|
|
"id": "number",
|
|
"username": "string",
|
|
"avatar": "string|null",
|
|
"role": "string",
|
|
"status": "string"
|
|
},
|
|
"ReadyVoiceState": {
|
|
"channel_id": "number",
|
|
"user_id": "number",
|
|
"muted": "boolean",
|
|
"deafened": "boolean"
|
|
},
|
|
"ReadyRole": {
|
|
"id": "number",
|
|
"name": "string",
|
|
"color": "string|null",
|
|
"permissions": "number"
|
|
}
|
|
}
|
|
},
|
|
"chat_message": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"id": "number",
|
|
"channel_id": "number",
|
|
"user": {
|
|
"id": "number",
|
|
"username": "string",
|
|
"avatar": "string|null",
|
|
"role": "string"
|
|
},
|
|
"content": "string",
|
|
"reply_to": "number|null",
|
|
"timestamp": "string",
|
|
"attachments": "array",
|
|
"reactions": "array",
|
|
"pinned": "boolean"
|
|
}
|
|
},
|
|
"chat_send_ok": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"message_id": "number",
|
|
"timestamp": "string"
|
|
}
|
|
},
|
|
"chat_edited": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"message_id": "number",
|
|
"channel_id": "number",
|
|
"content": "string",
|
|
"edited_at": "string"
|
|
}
|
|
},
|
|
"chat_deleted": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"message_id": "number",
|
|
"channel_id": "number"
|
|
}
|
|
},
|
|
"reaction_update": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"message_id": "number",
|
|
"channel_id": "number",
|
|
"emoji": "string",
|
|
"user_id": "number",
|
|
"action": "string"
|
|
}
|
|
},
|
|
"typing": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"user_id": "number",
|
|
"username": "string"
|
|
}
|
|
},
|
|
"presence": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user_id": "number",
|
|
"status": "string"
|
|
}
|
|
},
|
|
"channel_create": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"id": "number",
|
|
"name": "string",
|
|
"type": "string",
|
|
"category": "string",
|
|
"topic": "string",
|
|
"position": "number"
|
|
}
|
|
},
|
|
"channel_update": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"id": "number",
|
|
"name": "string",
|
|
"type": "string",
|
|
"category": "string",
|
|
"topic": "string",
|
|
"position": "number"
|
|
}
|
|
},
|
|
"channel_delete": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"id": "number"
|
|
}
|
|
},
|
|
"voice_state": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"user_id": "number",
|
|
"username": "string",
|
|
"muted": "boolean",
|
|
"deafened": "boolean",
|
|
"speaking": "boolean",
|
|
"camera": "boolean",
|
|
"screenshare": "boolean"
|
|
}
|
|
},
|
|
"voice_leave": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"user_id": "number"
|
|
}
|
|
},
|
|
"voice_config": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"quality": "string",
|
|
"bitrate": "number",
|
|
"max_users": "number"
|
|
}
|
|
},
|
|
"voice_token": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"token": "string",
|
|
"url": "string",
|
|
"direct_url": "string"
|
|
}
|
|
},
|
|
"voice_speakers": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"speakers": "array<number>",
|
|
"threshold_mode": "string"
|
|
}
|
|
},
|
|
"member_join": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user": {
|
|
"id": "number",
|
|
"username": "string",
|
|
"avatar": "string|null",
|
|
"role": "string"
|
|
}
|
|
}
|
|
},
|
|
"member_leave": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user_id": "number"
|
|
}
|
|
},
|
|
"member_update": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user_id": "number",
|
|
"role": "string"
|
|
}
|
|
},
|
|
"member_ban": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"user_id": "number"
|
|
}
|
|
},
|
|
"server_restart": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"reason": "string",
|
|
"delay_seconds": "number"
|
|
}
|
|
},
|
|
"error": {
|
|
"direction": "server_to_client",
|
|
"fields": {
|
|
"code": "string",
|
|
"message": "string"
|
|
}
|
|
}
|
|
},
|
|
"client_messages": {
|
|
"auth": {
|
|
"fields": {
|
|
"token": "string",
|
|
"last_seq": "number|undefined"
|
|
}
|
|
},
|
|
"chat_send": {
|
|
"fields": {
|
|
"channel_id": "number",
|
|
"content": "string",
|
|
"reply_to": "number|null",
|
|
"attachments": "array"
|
|
}
|
|
},
|
|
"chat_edit": {
|
|
"fields": {
|
|
"message_id": "number",
|
|
"content": "string"
|
|
}
|
|
},
|
|
"chat_delete": {
|
|
"fields": {
|
|
"message_id": "number"
|
|
}
|
|
},
|
|
"reaction_add": {
|
|
"fields": {
|
|
"message_id": "number",
|
|
"emoji": "string"
|
|
}
|
|
},
|
|
"reaction_remove": {
|
|
"fields": {
|
|
"message_id": "number",
|
|
"emoji": "string"
|
|
}
|
|
},
|
|
"typing_start": {
|
|
"fields": {
|
|
"channel_id": "number"
|
|
}
|
|
},
|
|
"channel_focus": {
|
|
"fields": {
|
|
"channel_id": "number"
|
|
}
|
|
},
|
|
"presence_update": {
|
|
"fields": {
|
|
"status": "string"
|
|
}
|
|
},
|
|
"voice_join": {
|
|
"fields": {
|
|
"channel_id": "number"
|
|
}
|
|
},
|
|
"voice_leave": {
|
|
"fields": {}
|
|
},
|
|
"voice_mute": {
|
|
"fields": {
|
|
"muted": "boolean"
|
|
}
|
|
},
|
|
"voice_deafen": {
|
|
"fields": {
|
|
"deafened": "boolean"
|
|
}
|
|
},
|
|
"voice_camera": {
|
|
"fields": {
|
|
"enabled": "boolean"
|
|
}
|
|
},
|
|
"voice_screenshare": {
|
|
"fields": {
|
|
"enabled": "boolean"
|
|
}
|
|
},
|
|
"ping": {
|
|
"fields": {}
|
|
}
|
|
},
|
|
"drift_notes": [
|
|
"chat_message: Go server sends user.role but TypeScript ChatMessagePayload uses MessageUser (no role). Client should add role to MessageUser or use UserWithRole.",
|
|
"chat_message: Go server sends reactions[] and pinned fields. TypeScript ChatMessagePayload is missing both. These fields exist on MessageResponse (REST) but not the WS payload.",
|
|
"channel_create: Go sends topic (string) but TypeScript ChannelCreatePayload omits topic. TypeScript also types category as string|null while Go sends empty string.",
|
|
"channel_update: Go sends full channel object (all 6 fields). TypeScript ChannelUpdatePayload only has id, optional name, optional position. Major drift.",
|
|
"voice_config: TypeScript VoiceConfigPayload has extra fields (threshold_mode, mixing_threshold, top_speakers) not present in Go voiceConfigPayload struct.",
|
|
"voice_token: TypeScript marks direct_url as optional. Go always sends it.",
|
|
"soundboard_play: TypeScript defines client message type but Go handler does not process it."
|
|
]
|
|
}
|