Skip to main content

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

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: 9999888877776666n,
      author_id: 123456789n,
      message: "Hello everyone!",
      chat_ref: { channel: { community_id: 111n, channel_id: 222n } }
    },
    channel_unread_count: 5,
    author: {
      id: 123456789n,
      name: "Alice",
      username: "alice"
    }
  }
}
message UpdateMessage { 
  tangle.client.types.Message message = 1; 
}
Sent when an existing message is edited.Example:
{
  message: {
    message: {
      message_id: 9999888877776666n,
      author_id: 123456789n,
      message: "Hello everyone! (edited)",
      edited_at: 1678901234567,
      chat_ref: { channel: { community_id: 111n, channel_id: 222n } }
    }
  }
}
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: 111n, channel_id: 222n } },
    message_ids: [9999888877776666n, 9999888877776665n]
  }
}
message UpdateMessageReactions {
  refs.ChatRef chat_ref = 1;
  reactions.MessageReactions reactions = 2;
}
Sent when reactions are added or removed from a message.

User & Status Updates

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: 123456789n,
    status: {
      status: 'ONLINE' // or 'IDLE'
    }
  }
}
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.).
message UpdateUserRelationship { 
  types.Relationship relationship = 1; 
}
Sent when a friendship is created, accepted, or blocked.
message UpdateUserRelationshipDeleted {
  // @snowflake<User>
  fixed64 other_user_id = 1;
}
Sent when a friendship is removed or a user is unblocked.

Community & Channel Updates

message UpdateCommunity {
  // @snowflake<Community>
  fixed64 community_id = 1;
  tangle.client.types.Community community = 2;
}
Sent when a community’s name, photo, or settings change.
message UpdateCommunityDeleted {
  // @snowflake<Community>
  fixed64 community_id = 1;
}
Sent when a community is deleted or the user is removed.
message UpdateChannel { 
  tangle.client.types.Channel channel = 1; 
}
Sent when a channel is created or modified (name, position, permissions).
message UpdateChannelDeleted { 
  refs.ChannelRef channel = 1; 
}
Sent when a channel is deleted.

Member & Permission Updates

message UpdateCommunityMemberCreated {
  // @snowflake<Community>
  fixed64 community_id = 1;
  // @snowflake<User>
  fixed64 member_id = 2;
  optional tangle.client.types.CommunityMember member = 3;
  optional tangle.client.types.User user = 4;
}
Sent when a new member joins a community.
message UpdateCommunityMember {
  // @snowflake<Community>
  fixed64 community_id = 1;
  // @snowflake<User>
  fixed64 member_id = 2;
  tangle.client.types.CommunityMember member = 3;
}
Sent when a member’s roles or nickname are updated.
message UpdateCommunityMemberDeleted {
  // @snowflake<Community>
  fixed64 community_id = 1;
  // @snowflake<User>
  fixed64 member_id = 2;
}
Sent when a member leaves or is removed from a community.
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

message UpdateChat { 
  chats.Chat chat = 1; 
}
Sent when a group chat’s name or settings change.
message UpdateGroup {
  types.Group group = 1;
  repeated types.User users = 2;
}
Sent when members are added or removed from a group chat.
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: 111n, channel_id: 222n } },
    user_id: 123456789n,
    typing: true
  }
}
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.
message UpdateConversationPermissions {
  refs.ChatRef chat_ref = 1;
  types.PermissionOverrides permissions = 2;
}
Sent when the user’s permissions change in a channel.

Session & System Updates

message UpdateSessionDeleted {
  // @snowflake<Session>
  fixed64 session_id = 1;
}
Sent when a session is revoked (logout from another device).
message UpdateCommunityUnavailable {
  // @snowflake<Community>
  fixed64 community_id = 1;
}
Sent when a community becomes temporarily unavailable (server issues).

Subscription Model

Clients automatically receive updates for:
1

Direct messages

All one-on-one conversations the user participates in
2

Group chats

All group chats the user is a member of
3

Community membership

All communities the user has joined
4

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: 0n, // 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

Build docs developers (and LLMs) love