Overview
Osmium Chat Protocol uses a push-based update system to notify clients about real-time events. Unlike RPC responses (which are replies to client requests), updates are server-initiated messages that inform clients about:
New messages in channels or chats
User status changes (online/offline)
Community and channel modifications
Typing indicators
Member joins/leaves
And much more
Update Message Structure
Updates are delivered through the ServerMessage type:
message ServerMessage {
uint32 id = 1 ;
oneof message {
tangle.client.updates.Update update = 2 ; // Real-time update
RPCResult result = 3 ; // RPC response
}
}
When the update field is set, it contains one of many possible update types:
message Update {
oneof update {
UpdateMessageCreated message_created = 1 ;
UpdateChannel channel = 2 ;
UpdateMessageDeleted message_deleted = 3 ;
UpdateUserStatus user_status = 4 ;
UpdateUser user = 5 ;
UpdateCommunity community = 6 ;
UpdateChannelDeleted channel_deleted = 7 ;
UpdateMessage message = 8 ;
UpdateChatTyping chat_typing = 9 ;
UpdateCommunityMember community_member = 10 ;
UpdateCommunityDeleted community_deleted = 11 ;
UpdateConversationPermissions conversation_permissions = 12 ;
UpdateChat chat = 13 ;
UpdateSessionDeleted session_deleted = 14 ;
UpdateCommunityUnavailable community_unavailable = 15 ;
UpdateMemberList member_list = 16 ;
UpdateCommunityMemberDeleted community_member_deleted = 17 ;
UpdateUserRelationship update_user_relationship = 18 ;
UpdateUserRelationshipDeleted update_user_relationship_deleted = 19 ;
UpdateGroup group = 20 ;
voice.UpdateRoomState room_state = 21 ;
voice.UpdateRoomParticipant room_participant = 22 ;
UpdateMessageReactions message_reactions = 23 ;
UpdateConversationLastRead conversation_last_read = 24 ;
UpdateCommunityMemberCreated community_member_created = 25 ;
}
}
Update Categories
Message Updates
UpdateMessageCreated - New message arrives
message UpdateMessageCreated {
tangle.client.types.Message message = 1 ;
uint32 channel_unread_count = 2 ;
optional tangle.client.types.User author = 3 ;
}
Sent when a new message is created in any channel/chat the user has access to. Fields:
message: Complete message object
channel_unread_count: Updated unread count for this channel
author: User who sent the message (especially useful in communities)
Example: {
message_created : {
message : {
message_id : 9999888877776666 n ,
author_id : 123456789 n ,
message : "Hello everyone!" ,
chat_ref : { channel : { community_id : 111 n , channel_id : 222 n } }
},
channel_unread_count : 5 ,
author : {
id : 123456789 n ,
name : "Alice" ,
username : "alice"
}
}
}
UpdateMessage - Message edited
message UpdateMessage {
tangle.client.types.Message message = 1 ;
}
Sent when an existing message is edited. Example: {
message : {
message : {
message_id : 9999888877776666 n ,
author_id : 123456789 n ,
message : "Hello everyone! (edited)" ,
edited_at : 1678901234567 ,
chat_ref : { channel : { community_id : 111 n , channel_id : 222 n } }
}
}
}
UpdateMessageDeleted - Messages removed
message UpdateMessageDeleted {
refs.ChatRef chat_ref = 1 ;
// @snowflake<Message>
repeated fixed64 message_ids = 2 ;
}
Sent when one or more messages are deleted. Can include multiple message IDs for bulk deletion. Example: {
message_deleted : {
chat_ref : { channel : { community_id : 111 n , channel_id : 222 n } },
message_ids : [ 9999888877776666 n , 9999888877776665 n ]
}
}
UpdateMessageReactions - Reaction changes
message UpdateMessageReactions {
refs.ChatRef chat_ref = 1 ;
reactions.MessageReactions reactions = 2 ;
}
Sent when reactions are added or removed from a message.
User & Status Updates
UpdateUserStatus - Online/idle status changes
message UpdateUserStatus {
// @snowflake<User>
fixed64 user_id = 1 ;
tangle.client.types.UserStatus status = 2 ;
}
Sent when a user’s online status changes. Example: {
user_status : {
user_id : 123456789 n ,
status : {
status : 'ONLINE' // or 'IDLE'
}
}
}
UpdateUser - Profile changes
message UpdateUser {
// @snowflake<User>
fixed64 user_id = 1 ;
tangle.client.types.User user = 2 ;
}
Sent when a user updates their profile (name, username, avatar, etc.).
UpdateUserRelationship - Friend status changes
message UpdateUserRelationship {
types.Relationship relationship = 1 ;
}
Sent when a friendship is created, accepted, or blocked.
UpdateUserRelationshipDeleted - Friendship removed
message UpdateUserRelationshipDeleted {
// @snowflake<User>
fixed64 other_user_id = 1 ;
}
Sent when a friendship is removed or a user is unblocked.
UpdateCommunity - Community modified
UpdateCommunityDeleted - Community removed
UpdateChannel - Channel modified
message UpdateChannel {
tangle.client.types.Channel channel = 1 ;
}
Sent when a channel is created or modified (name, position, permissions).
UpdateChannelDeleted - Channel removed
message UpdateChannelDeleted {
refs.ChannelRef channel = 1 ;
}
Sent when a channel is deleted.
Member & Permission Updates
UpdateCommunityMemberCreated - Member joins
UpdateCommunityMember - Member roles/nickname change
UpdateCommunityMemberDeleted - Member leaves/removed
UpdateMemberList - Member list changes
message UpdateMemberList {
optional fixed64 community_id = 1 ;
fixed64 channel_id = 2 ;
repeated MemberListEntry entries = 4 ;
}
Sent when viewing a channel to provide the member list sidebar.
Chat & Conversation Updates
UpdateChat - Group chat modified
message UpdateChat {
chats.Chat chat = 1 ;
}
Sent when a group chat’s name or settings change.
UpdateGroup - Group membership changes
message UpdateGroup {
types.Group group = 1 ;
repeated types.User users = 2 ;
}
Sent when members are added or removed from a group chat.
UpdateChatTyping - Typing indicator
message UpdateChatTyping {
refs.ChatRef chat_ref = 1 ;
// @snowflake<User>
fixed64 user_id = 2 ;
bool typing = 3 ;
}
Sent when a user starts or stops typing. Example: {
chat_typing : {
chat_ref : { channel : { community_id : 111 n , channel_id : 222 n } },
user_id : 123456789 n ,
typing : true
}
}
UpdateConversationLastRead - Read receipt
message UpdateConversationLastRead {
refs.ChatRef chat_ref = 1 ;
// @snowflake<Message>
fixed64 last_read_message_id = 2 ;
optional uint32 unread_count = 3 ;
}
Sent when the user marks messages as read in another session.
UpdateConversationPermissions - Permission changes
message UpdateConversationPermissions {
refs.ChatRef chat_ref = 1 ;
types.PermissionOverrides permissions = 2 ;
}
Sent when the user’s permissions change in a channel.
Session & System Updates
UpdateSessionDeleted - Session revoked
message UpdateSessionDeleted {
// @snowflake<Session>
fixed64 session_id = 1 ;
}
Sent when a session is revoked (logout from another device).
UpdateCommunityUnavailable - Server issues
Subscription Model
Clients automatically receive updates for:
Direct messages
All one-on-one conversations the user participates in
Group chats
All group chats the user is a member of
Community membership
All communities the user has joined
Friend relationships
Status updates for all friends
Disabling Updates
When initializing the connection, you can opt out of automatic subscriptions:
message Initialize {
uint32 client_id = 1 ;
string device_type = 2 ;
string device_version = 3 ;
string app_version = 4 ;
bool no_subscribe = 5 ; // Set to true to disable automatic updates
}
Setting no_subscribe = true is useful for bots or backend services that only need to make RPC calls without receiving real-time updates.
Handling Updates in Code
Basic Update Handler
class UpdateHandler {
handleServerMessage ( serverMessage ) {
if ( serverMessage . update ) {
this . handleUpdate ( serverMessage . update );
} else if ( serverMessage . result ) {
this . handleRPCResult ( serverMessage . result );
}
}
handleUpdate ( update ) {
// Check which update type it is
if ( update . message_created ) {
this . onNewMessage ( update . message_created );
} else if ( update . message_deleted ) {
this . onMessageDeleted ( update . message_deleted );
} else if ( update . user_status ) {
this . onUserStatusChange ( update . user_status );
} else if ( update . chat_typing ) {
this . onTypingIndicator ( update . chat_typing );
}
// ... handle other update types
}
onNewMessage ( messageCreated ) {
const { message , channel_unread_count , author } = messageCreated ;
// Add to message list
this . addMessageToChat ( message );
// Update unread count
this . updateUnreadCount ( message . chat_ref , channel_unread_count );
// Show notification if not current channel
if ( ! this . isCurrentChannel ( message . chat_ref )) {
this . showNotification ( message , author );
}
}
onMessageDeleted ( messageDeleted ) {
const { chat_ref , message_ids } = messageDeleted ;
// Remove from UI
for ( const messageId of message_ids ) {
this . removeMessageFromChat ( chat_ref , messageId );
}
}
onUserStatusChange ( userStatus ) {
const { user_id , status } = userStatus ;
// Update user status in UI
this . updateUserPresence ( user_id , status );
}
onTypingIndicator ( chatTyping ) {
const { chat_ref , user_id , typing } = chatTyping ;
if ( typing ) {
this . addTypingUser ( chat_ref , user_id );
// Clear after timeout
setTimeout (() => {
this . removeTypingUser ( chat_ref , user_id );
}, 5000 );
} else {
this . removeTypingUser ( chat_ref , user_id );
}
}
}
React/Vue Event-Driven Pattern
import { EventEmitter } from 'events' ;
class OsmiumClient extends EventEmitter {
handleUpdate ( update : Update ) {
if ( update . message_created ) {
this . emit ( 'message' , update . message_created . message );
} else if ( update . message_deleted ) {
this . emit ( 'messageDelete' , update . message_deleted );
} else if ( update . user_status ) {
this . emit ( 'userStatus' , update . user_status );
}
// ... etc
}
}
// In React component
function ChatView () {
const [ messages , setMessages ] = useState ([]);
useEffect (() => {
const handleNewMessage = ( message ) => {
setMessages ( prev => [ ... prev , message ]);
};
const handleMessageDelete = ({ message_ids }) => {
setMessages ( prev =>
prev . filter ( m => ! message_ids . includes ( m . message_id ))
);
};
client . on ( 'message' , handleNewMessage );
client . on ( 'messageDelete' , handleMessageDelete );
return () => {
client . off ( 'message' , handleNewMessage );
client . off ( 'messageDelete' , handleMessageDelete );
};
}, []);
return (
< div >
{ messages . map ( msg => < MessageComponent key ={ msg . message_id } message ={ msg } />)}
</ div >
);
}
State Management Integration (Redux/Vuex)
// Redux action creators
const handleUpdate = ( update : Update ) => ( dispatch ) => {
if ( update . message_created ) {
dispatch ({
type: 'MESSAGE_CREATED' ,
payload: update . message_created
});
} else if ( update . message_deleted ) {
dispatch ({
type: 'MESSAGE_DELETED' ,
payload: update . message_deleted
});
} else if ( update . user_status ) {
dispatch ({
type: 'USER_STATUS_CHANGED' ,
payload: update . user_status
});
}
// ... etc
};
// Reducer
const messagesReducer = ( state = {}, action ) => {
switch ( action . type ) {
case 'MESSAGE_CREATED' :
const { message } = action . payload ;
return {
... state ,
[message.message_id]: message
};
case 'MESSAGE_DELETED' :
const newState = { ... state };
for ( const messageId of action . payload . message_ids ) {
delete newState [ messageId ];
}
return newState ;
default :
return state ;
}
};
Update Processing Best Practices
Idempotent Processing Updates may be received multiple times. Design handlers to be idempotent - applying the same update twice should have no additional effect.
Order Independence Don’t assume updates arrive in order. Use Snowflake IDs and timestamps to determine the actual sequence of events.
Batch UI Updates Accumulate multiple updates before triggering UI re-renders to avoid performance issues.
Offline Resilience When reconnecting, fetch latest state via RPC rather than relying on missed updates.
Common Patterns
Typing Indicator with Debounce
class TypingManager {
constructor ( client ) {
this . client = client ;
this . typingTimers = new Map ();
}
startTyping ( chatRef ) {
const key = this . getChatKey ( chatRef );
// Send typing indicator
if ( ! this . typingTimers . has ( key )) {
this . client . sendRequest ({
chats_set_typing: {
chat_ref: chatRef ,
typing: true
}
});
}
// Clear existing timer
clearTimeout ( this . typingTimers . get ( key ));
// Auto-stop after 5 seconds
this . typingTimers . set ( key , setTimeout (() => {
this . stopTyping ( chatRef );
}, 5000 ));
}
stopTyping ( chatRef ) {
const key = this . getChatKey ( chatRef );
clearTimeout ( this . typingTimers . get ( key ));
this . typingTimers . delete ( key );
this . client . sendRequest ({
chats_set_typing: {
chat_ref: chatRef ,
typing: false
}
});
}
}
Optimistic UI Updates
class MessageSender {
async sendMessage ( chatRef , text ) {
// Generate temporary ID
const tempId = `temp- ${ Date . now () } - ${ Math . random () } ` ;
// Optimistically add to UI
const optimisticMessage = {
message_id: 0 n , // Placeholder
temp_id: tempId ,
message: text ,
author_id: this . currentUserId ,
chat_ref: chatRef ,
pending: true
};
this . addMessageToUI ( optimisticMessage );
try {
// Send actual request
const result = await this . client . sendRequest ({
messages_send_message: {
chat_ref: chatRef ,
message: text ,
temp_id: tempId
}
});
// Replace optimistic message with real one
this . replaceMessage ( tempId , result . sent_message . message );
} catch ( error ) {
// Mark message as failed
this . markMessageFailed ( tempId , error );
}
}
}
Client-Server Communication Learn about the RPC system that complements real-time updates
Message Flow Understand how requests and updates flow through the system
Snowflake IDs Learn about the ID system used in update messages