mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* feat(b2-2): declare protocol_epoch in the schema and generate both constants protocol/schema.json gains protocol_epoch (1). genprotocol emits ws.ProtocolEpoch and PROTOCOL_EPOCH from it; the contract test pins the Go constant to the schema so a stale regeneration fails the required check. * feat(b2-2): check the client's protocol epoch in the auth handshake The auth payload gains epoch (absent = 0). Outside [minClientEpoch, ProtocolEpoch] the server answers one auth_error with code protocol_epoch_unsupported, the client/server/min epochs, and a message naming which side to update, then closes 1008 like every other handshake failure. minClientEpoch is 0 for epoch 1 only so alpha.4 clients keep connecting; the epoch-1 fixtures are unchanged. * feat(b2-2): send the protocol epoch and offer the update on a refused connect ws.ts sends epoch: PROTOCOL_EPOCH in the auth frame (contract test extended on purpose). On auth_error code protocol_epoch_unsupported with a newer server the dispatcher records the host in ui.store.updateRequiredHost and main.ts mounts the UpdateNotifier on the connect page, so a refused client gets the same Update Now banner it would have had on the main page. * feat(b2-2): withhold client releases newer than the server's protocol epoch The signed server-update manifest gains protocol_epoch (release.yml reads it from protocol/schema.json). Updater.ReleaseProtocolEpoch verifies the manifest and reads it; the client-update endpoint answers 204 when the release's epoch is newer than ws.ProtocolEpoch or the manifest does not verify. Releases without a manifest are epoch 0 and advertised as before. Docs: protocol.md Compatibility section, api.md, deployment.md, protocol README, CHANGELOG Unreleased. * docs(b2-2): record the slim B2-2 decision and evidence; fold B2-3/B2-4 into it * ci: prove the protocol_epoch manifest read on every PR, not only at tag time * fix(b2-2): offer the update on an already-mounted connect page and keep the credential on a protocol refusal Codex P1: on a first login or startup auto-login no overlay exists before auth_ok, so a refusal never re-rendered the connect page and the one-time read of updateRequiredHost missed it. The connect page now subscribes to it, and a later refusal replaces the banner. Codex P2: a refusal on reconnect went through the generic logout and deleted the stored credential although the token is still valid. clearAuth gets a protocol_epoch reason; main.ts keeps the credential on it (the skip-auto-login flag is still set and, being sessionStorage, does not survive the relaunch the update triggers).
113 lines
6.2 KiB
JSON
113 lines
6.2 KiB
JSON
{
|
|
"$comment": "Single source of truth for WebSocket protocol message-type constants. Server/ws/message_types.go and Client/src/lib/protocolTypes.ts are generated from this file — edit here, then run `npm run generate` from the repository root (or `make protocol-generate` in Server/). CI runs `make protocol-verify` to reject drift.",
|
|
"version": 1,
|
|
"protocol_epoch": 1,
|
|
"client_to_server": [
|
|
{ "wire": "auth", "go": "MsgTypeAuth", "ts": "AUTH" },
|
|
{ "wire": "chat_send", "go": "MsgTypeChatSend", "ts": "CHAT_SEND" },
|
|
{ "wire": "chat_edit", "go": "MsgTypeChatEdit", "ts": "CHAT_EDIT" },
|
|
{ "wire": "chat_delete", "go": "MsgTypeChatDelete", "ts": "CHAT_DELETE" },
|
|
{ "wire": "reaction_add", "go": "MsgTypeReactionAdd", "ts": "REACTION_ADD" },
|
|
{ "wire": "reaction_remove", "go": "MsgTypeReactionRemove", "ts": "REACTION_REMOVE" },
|
|
{ "wire": "typing_start", "go": "MsgTypeTypingStart", "ts": "TYPING_START" },
|
|
{ "wire": "channel_focus", "go": "MsgTypeChannelFocus", "ts": "CHANNEL_FOCUS" },
|
|
{ "wire": "mark_read", "go": "MsgTypeMarkRead", "ts": "MARK_READ" },
|
|
{ "wire": "presence_update", "go": "MsgTypePresenceUpdate", "ts": "PRESENCE_UPDATE" },
|
|
{ "wire": "voice_join", "go": "MsgTypeVoiceJoin", "ts": "VOICE_JOIN" },
|
|
{ "wire": "voice_leave", "go": "MsgTypeVoiceLeave", "ts": "VOICE_LEAVE" },
|
|
{ "wire": "voice_mute", "go": "MsgTypeVoiceMute", "ts": "VOICE_MUTE" },
|
|
{ "wire": "voice_deafen", "go": "MsgTypeVoiceDeafen", "ts": "VOICE_DEAFEN" },
|
|
{ "wire": "voice_camera", "go": "MsgTypeVoiceCamera", "ts": "VOICE_CAMERA" },
|
|
{ "wire": "voice_screenshare", "go": "MsgTypeVoiceScreenshare", "ts": "VOICE_SCREENSHARE" },
|
|
{ "wire": "voice_mod_mute", "go": "MsgTypeVoiceModMute", "ts": "VOICE_MOD_MUTE" },
|
|
{ "wire": "voice_mod_deafen", "go": "MsgTypeVoiceModDeafen", "ts": "VOICE_MOD_DEAFEN" },
|
|
{ "wire": "voice_mod_move", "go": "MsgTypeVoiceModMove", "ts": "VOICE_MOD_MOVE" },
|
|
{ "wire": "voice_mod_kick", "go": "MsgTypeVoiceModKick", "ts": "VOICE_MOD_KICK" },
|
|
{ "wire": "ping", "go": "MsgTypePing", "ts": "PING" },
|
|
{
|
|
"wire": "voice_token_refresh",
|
|
"go": "MsgTypeVoiceTokenRefresh",
|
|
"ts": "VOICE_TOKEN_REFRESH",
|
|
"go_trailing_comment": "//nolint:gosec // G101: false positive — message type constant, not a credential"
|
|
},
|
|
{
|
|
"wire": "voice_e2ee_announce",
|
|
"go": "MsgTypeVoiceE2EEAnnounce",
|
|
"ts": "VOICE_E2EE_ANNOUNCE"
|
|
},
|
|
{ "wire": "voice_e2ee_offer", "go": "MsgTypeVoiceE2EEOffer", "ts": "VOICE_E2EE_OFFER" },
|
|
{ "wire": "call_ring", "go": "MsgTypeCallRing", "ts": "CALL_RING" },
|
|
{ "wire": "call_decline", "go": "MsgTypeCallDecline", "ts": "CALL_DECLINE" },
|
|
{
|
|
"wire": "chat_command",
|
|
"go": "MsgTypeChatCommand",
|
|
"ts": "CHAT_COMMAND",
|
|
"note": "plugin slash-command dispatch (Phase C)"
|
|
}
|
|
],
|
|
"server_to_client": [
|
|
{ "wire": "auth_ok", "go": "MsgTypeAuthOK", "ts": "AUTH_OK" },
|
|
{ "wire": "auth_error", "go": "MsgTypeAuthError", "ts": "AUTH_ERROR" },
|
|
{ "wire": "ready", "go": "MsgTypeReady", "ts": "READY" },
|
|
{ "wire": "chat_message", "go": "MsgTypeChatMessage", "ts": "CHAT_MESSAGE" },
|
|
{ "wire": "chat_send_ok", "go": "MsgTypeChatSendOK", "ts": "CHAT_SEND_OK" },
|
|
{ "wire": "chat_edited", "go": "MsgTypeChatEdited", "ts": "CHAT_EDITED" },
|
|
{ "wire": "chat_deleted", "go": "MsgTypeChatDeleted", "ts": "CHAT_DELETED" },
|
|
{ "wire": "chat_bulk_deleted", "go": "MsgTypeChatBulkDeleted", "ts": "CHAT_BULK_DELETED" },
|
|
{ "wire": "reaction_update", "go": "MsgTypeReactionUpdate", "ts": "REACTION_UPDATE" },
|
|
{ "wire": "typing", "go": "MsgTypeTyping", "ts": "TYPING" },
|
|
{ "wire": "presence", "go": "MsgTypePresence", "ts": "PRESENCE" },
|
|
{ "wire": "channel_create", "go": "MsgTypeChannelCreate", "ts": "CHANNEL_CREATE" },
|
|
{ "wire": "channel_update", "go": "MsgTypeChannelUpdate", "ts": "CHANNEL_UPDATE" },
|
|
{ "wire": "channel_delete", "go": "MsgTypeChannelDelete", "ts": "CHANNEL_DELETE" },
|
|
{ "wire": "voice_state", "go": "MsgTypeVoiceState", "ts": "VOICE_STATE" },
|
|
{ "wire": "voice_config", "go": "MsgTypeVoiceConfig", "ts": "VOICE_CONFIG" },
|
|
{ "wire": "voice_token", "go": "MsgTypeVoiceToken", "ts": "VOICE_TOKEN" },
|
|
{
|
|
"wire": "voice_leave",
|
|
"go": "MsgTypeVoiceLeaveBC",
|
|
"ts": "VOICE_LEAVE",
|
|
"note": "broadcast (same string as client msg)"
|
|
},
|
|
{ "wire": "voice_moved", "go": "MsgTypeVoiceMoved", "ts": "VOICE_MOVED" },
|
|
{ "wire": "voice_disconnected", "go": "MsgTypeVoiceDisconnected", "ts": "VOICE_DISCONNECTED" },
|
|
{ "wire": "member_join", "go": "MsgTypeMemberJoin", "ts": "MEMBER_JOIN" },
|
|
{ "wire": "member_update", "go": "MsgTypeMemberUpdate", "ts": "MEMBER_UPDATE" },
|
|
{ "wire": "user_update", "go": "MsgTypeUserUpdate", "ts": "USER_UPDATE" },
|
|
{ "wire": "member_ban", "go": "MsgTypeMemberBan", "ts": "MEMBER_BAN" },
|
|
{ "wire": "roles_update", "go": "MsgTypeRolesUpdate", "ts": "ROLES_UPDATE" },
|
|
{ "wire": "emoji_update", "go": "MsgTypeEmojiUpdate", "ts": "EMOJI_UPDATE" },
|
|
{ "wire": "server_restart", "go": "MsgTypeServerRestart", "ts": "SERVER_RESTART" },
|
|
{ "wire": "error", "go": "MsgTypeError", "ts": "ERROR" },
|
|
{ "wire": "pong", "go": "MsgTypePong", "ts": "PONG" },
|
|
{ "wire": "dm_channel_open", "go": "MsgTypeDMChannelOpen", "ts": "DM_CHANNEL_OPEN" },
|
|
{ "wire": "dm_channel_close", "go": "MsgTypeDMChannelClose", "ts": "DM_CHANNEL_CLOSE" },
|
|
{ "wire": "call_incoming", "go": "MsgTypeCallIncoming", "ts": "CALL_INCOMING" },
|
|
{ "wire": "call_declined", "go": "MsgTypeCallDeclined", "ts": "CALL_DECLINED" },
|
|
{
|
|
"wire": "voice_e2ee_announce",
|
|
"go": "MsgTypeVoiceE2EEAnnounceBC",
|
|
"ts": "VOICE_E2EE_ANNOUNCE",
|
|
"note": "broadcast (same string as client msg)"
|
|
},
|
|
{
|
|
"wire": "voice_e2ee_offer",
|
|
"go": "MsgTypeVoiceE2EEOfferRelay",
|
|
"ts": "VOICE_E2EE_OFFER",
|
|
"note": "relay (same string as client msg)"
|
|
},
|
|
{
|
|
"wire": "command_reply",
|
|
"go": "MsgTypeCommandReply",
|
|
"ts": "COMMAND_REPLY",
|
|
"note": "ephemeral plugin reply, sent only to the invoking client"
|
|
},
|
|
{
|
|
"wire": "plugin_broadcast",
|
|
"go": "MsgTypePluginBroadcast",
|
|
"ts": "PLUGIN_BROADCAST",
|
|
"note": "plugin channel broadcast, gated by the sender's SEND_MESSAGES"
|
|
}
|
|
]
|
|
}
|