| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- syntax = "proto3";
- package chatto.realtime.v1;
- import "chatto/api/v1/notifications.proto";
- import "chatto/api/v1/member_directory.proto";
- import "chatto/api/v1/message_types.proto";
- import "chatto/api/v1/presence.proto";
- import "chatto/api/v1/room_directory.proto";
- import "chatto/api/v1/room_timeline.proto";
- import "chatto/api/v1/server.proto";
- import "chatto/api/v1/server_state.proto";
- import "chatto/api/v1/viewer.proto";
- import "chatto/api/v1/voice_calls.proto";
- import "google/protobuf/timestamp.proto";
- // Client-to-server frame for Chatto's protobuf WebSocket realtime protocol.
- //
- // Clients send binary protobuf frames to `/api/realtime`. The first frame must
- // be `hello`; after the server replies with `hello`, clients send
- // `subscribe_events` to start the authenticated server-projection stream.
- message RealtimeClientFrame {
- reserved "ack";
- oneof frame {
- // Opens the protocol session and optionally carries a bearer token.
- RealtimeClientHello hello = 1;
- // Starts the caller's authorized server-projection stream.
- RealtimeSubscribeEvents subscribe_events = 2;
- // Application-level ping. The server replies with `pong`.
- RealtimePing ping = 3;
- // Adds one joined room's recent timeline to the retained projection.
- RealtimeHydrateRoom hydrate_room = 4;
- }
- }
- // Server-to-client frame for Chatto's protobuf WebSocket realtime protocol.
- message RealtimeServerFrame {
- oneof frame {
- // Confirms protocol version, server version, and capabilities.
- RealtimeServerHello hello = 1;
- // Confirms the server-projection stream has started.
- RealtimeSubscribed subscribed = 2;
- // An authorized transient, non-replayable event.
- RealtimeEventEnvelope event = 3;
- // Application-level heartbeat used for liveness checks.
- RealtimeHeartbeat heartbeat = 4;
- // Protocol or authorization error.
- RealtimeError error = 5;
- // Server-requested close with reconnect guidance.
- RealtimeClose close = 6;
- // Reply to a client ping.
- RealtimePong pong = 7;
- // Confirms that durable replay reached the live-stream boundary.
- RealtimeCaughtUp caught_up = 8;
- // One idempotent mutation of the caller's server projection.
- RealtimeProjectionEvent projection_event = 9;
- }
- }
- // Initial client hello.
- message RealtimeClientHello {
- reserved 3;
- reserved "resume_cursor";
- // Protocol version requested by the client. The only supported version is 2.
- uint32 protocol_version = 1;
- // Optional bearer token. When present, it takes precedence over cookie auth.
- optional string bearer_token = 2;
- }
- // Initial server hello.
- message RealtimeServerHello {
- reserved 3;
- reserved "resume_supported";
- // Protocol version accepted by the server.
- uint32 protocol_version = 1;
- // Chatto server software version.
- string server_version = 2;
- // Approximate heartbeat interval clients should expect.
- uint32 heartbeat_interval_seconds = 4;
- // Stable protocol capability keys supported by this server. Current keys:
- // `chatto.realtime.events.live.v1`, `chatto.realtime.heartbeat.v1`,
- // `chatto.realtime.ping.v1`, `chatto.realtime.events.resume.v1`, and
- // `chatto.realtime.projection.v1`.
- repeated string capabilities = 5;
- }
- // Request to start the caller's authorized server-projection stream.
- message RealtimeSubscribeEvents {
- // Opaque cursor from a previously received durable event or caught-up
- // frame. The server replays authorized durable projection changes after
- // this cursor before continuing with live delivery. Omit it to receive a
- // compacted reset of the current authorised server projection. Cursors
- // expire 24 hours after issue; an expired cursor also receives a compacted
- // reset rather than historical replay.
- optional string resume_cursor = 1;
- // Joined rooms whose timeline windows the client already retains. A fresh
- // compacted projection includes only these timelines, and resumed delivery
- // emits timeline mutations only for these rooms. Lightweight room, unread,
- // notification, and call state remains server-wide. At most 64 room IDs
- // may be supplied in one subscription.
- repeated string retained_room_ids = 2;
- }
- // Requests lazy materialisation of one joined room's recent timeline.
- //
- // The server replies on the same ordered projection stream with a
- // `room_timeline_replace` operation. Once hydrated, later timeline mutations
- // for the room are included for the lifetime of this connection. Clients send
- // all still-retained room IDs again in their next subscription. A connection
- // may retain at most 64 distinct room IDs; excess requests receive a
- // non-fatal `too_many_retained_rooms` error.
- message RealtimeHydrateRoom {
- // Joined room to materialise. Repeating a retained room is idempotent.
- string room_id = 1;
- }
- // Confirms projection streaming has started.
- message RealtimeSubscribed {
- reserved 1;
- reserved "cursor";
- // Opaque cursor from which this subscription starts. When the request
- // supplied a resume cursor, this is that validated cursor. Otherwise it is
- // the current live boundary.
- optional string start_cursor = 2;
- }
- // Confirms that durable replay is complete and live delivery has begun.
- //
- // Clients can retain `cursor` after applying every preceding event. A cursor
- // must not outlive the projection state it describes. Events after this frame
- // are live. Transient events are never replayed. A cursor is usable for up to
- // 24 hours after issue and should be replaced by each later caught-up cursor.
- message RealtimeCaughtUp {
- // Opaque cursor at the replay-to-live handoff boundary.
- string cursor = 1;
- }
- // One ordered, idempotent mutation of the caller's authorised server view.
- //
- // A fresh subscription starts with a reset operation followed by current
- // resource upserts. A resumed subscription derives operations from EVT after
- // the supplied cursor. Both paths use this envelope and the same client
- // reducer. The stream is a convergence feed, not an immutable audit log:
- // replay uses current authorisation, deletion, and erasure state.
- message RealtimeProjectionEvent {
- // Stable event ID. Synthetic compacted-replay events use server-generated IDs.
- string id = 1;
- // Time the source fact occurred or the compacted event was generated.
- google.protobuf.Timestamp created_at = 2;
- // User or system actor that caused the source fact, when known.
- optional string actor_id = 3;
- // Opaque cursor safe to retain with the resulting projection state after
- // applying every operation in this event.
- optional string resume_cursor = 4;
- // Operations applied atomically and in order by the client reducer.
- repeated RealtimeProjectionOperation operations = 5;
- }
- // One mutation of a server-scoped client projection.
- message RealtimeProjectionOperation {
- reserved 13;
- reserved "notifications_replace";
- oneof operation {
- // Clears all canonical server projection state before compacted replay.
- RealtimeProjectionReset reset = 1;
- // Replaces the public server profile.
- chatto.api.v1.ServerPublicProfile server_upsert = 2;
- // Replaces the authenticated viewer resource.
- chatto.api.v1.GetViewerResponse viewer_upsert = 3;
- // Adds or replaces one server directory member.
- chatto.api.v1.DirectoryMember user_upsert = 4;
- // Removes one server directory member.
- RealtimeProjectionUserRemove user_remove = 5;
- // Adds or replaces one room and its viewer state. Channel membership is
- // complete only after that room's timeline has been materialised.
- RealtimeProjectionRoom room_upsert = 6;
- // Removes one room and all locally retained room state.
- RealtimeProjectionRoomRemove room_remove = 7;
- // Replaces the complete visible room-group layout.
- RealtimeProjectionRoomGroupsReplace room_groups_replace = 8;
- // Replaces the lazily retained recent timeline window for one joined room.
- RealtimeProjectionRoomTimelineReplace room_timeline_replace = 9;
- // Adds or replaces one renderable event in a retained room timeline.
- RealtimeProjectionRoomTimelineEventUpsert room_timeline_event_upsert = 10;
- // Replaces authenticated server-wide presentation and runtime state.
- RealtimeProjectionServerState server_state_upsert = 11;
- // Removes one event from a retained room timeline. This is distinct from
- // a message tombstone: projection-only channel echoes disappear when the
- // underlying thread reply is no longer echoed into the room.
- RealtimeProjectionRoomTimelineEventRemove room_timeline_event_remove = 12;
- // Replaces one room's current viewer state without
- // retransmitting room metadata or membership.
- RealtimeProjectionRoomViewerStateReplace room_viewer_state_replace = 14;
- // Replaces every active call visible to the caller.
- RealtimeProjectionActiveCallsReplace active_calls_replace = 15;
- // Replaces the latest presence status for every server member. Presence
- // is transient and not replayed from EVT, so every subscription emits
- // this reconciliation before caught_up.
- RealtimeProjectionPresencesReplace presences_replace = 16;
- // Replaces viewer-specific follow and unread state for every followed
- // thread. Missing entries authoritatively mean not followed and not
- // unread for retained thread roots.
- RealtimeProjectionThreadViewerStatesReplace thread_viewer_states_replace = 17;
- // Signals root-message activity for lightweight room ordering even when
- // that room's timeline is not retained.
- RealtimeProjectionRoomActivity room_activity = 18;
- // Replaces the viewer's current notification-occurrence page and exact
- // unread occurrence counts. This uses a new top-level operation tag rather
- // than reinterpreting the released Notifications 1.0 replacement.
- RealtimeProjectionNotificationOccurrencesReplace notification_occurrences_replace = 19;
- }
- }
- // Reset marker for a compacted projection replay.
- message RealtimeProjectionReset {}
- // Authenticated server state required by every signed-in client.
- message RealtimeProjectionServerState {
- // Optional message of the day shown to authenticated members.
- optional string motd = 1;
- // Runtime capabilities and limits advertised to the client.
- chatto.api.v1.ServerRuntimeConfig runtime = 2;
- // Live pin transition that caused this replacement, when applicable.
- // Bootstrap and replay reconciliation omit this field.
- optional RealtimeProjectionPinnedMessageChange pinned_message_change = 3;
- }
- // Content-free pinned-message transition used to refresh room pin stores and
- // drive the client-local unseen indicator.
- message RealtimeProjectionPinnedMessageChange {
- RealtimeProjectionPinnedMessageAction action = 1;
- string room_id = 2;
- string message_event_id = 3;
- }
- enum RealtimeProjectionPinnedMessageAction {
- REALTIME_PROJECTION_PINNED_MESSAGE_ACTION_UNSPECIFIED = 0;
- REALTIME_PROJECTION_PINNED_MESSAGE_ACTION_CREATED = 1;
- REALTIME_PROJECTION_PINNED_MESSAGE_ACTION_DELETED = 2;
- }
- // Canonical lightweight room state retained by a server-scoped projection.
- message RealtimeProjectionRoom {
- // Public room metadata and viewer-specific state.
- chatto.api.v1.RoomWithViewerState room = 1;
- // Current room membership expressed as references into the projection's
- // server-wide user directory. DM participants are always present. Channel
- // membership is empty in a cold room summary and complete after that room's
- // timeline has been materialised.
- repeated string member_user_ids = 2;
- reserved 3;
- reserved "viewer_notification_count";
- // Whether this DM room has ever received a root message. False means the
- // durable room exists only so its initiator can compose the first message.
- // Once true, message deletion does not reset it. Absent for channel rooms
- // and servers that predate this field.
- optional bool has_message_history = 4;
- }
- // Root-message activity that affects lightweight room navigation state.
- //
- // This deliberately carries no message content. Retained rooms receive the
- // corresponding timeline mutation separately; unretained rooms still use this
- // operation to keep DM ordering current without materialising their timeline.
- message RealtimeProjectionRoomActivity {
- string room_id = 1;
- }
- // Removes one user from the client projection. Reducers must also purge copied
- // render data for this user from retained rooms, timelines, notifications, and
- // calls while preserving stable IDs on historical facts.
- message RealtimeProjectionUserRemove {
- // Stable user ID to remove.
- string user_id = 1;
- }
- // Complete latest-value presence state for the projected user directory.
- // Missing map entries are offline; the server normally includes every member
- // explicitly so reducers can converge without retaining an older value.
- message RealtimeProjectionPresencesReplace {
- map<string, chatto.api.v1.PresenceStatus> statuses = 1;
- }
- // Complete current viewer state for followed threads. This reconciles durable
- // follow changes and RUNTIME_STATE read markers that may change while a client
- // is dormant without retransmitting room timelines.
- message RealtimeProjectionThreadViewerStatesReplace {
- repeated RealtimeProjectionThreadViewerState states = 1;
- }
- // Viewer-specific state for one followed thread root.
- message RealtimeProjectionThreadViewerState {
- string room_id = 1;
- string thread_root_event_id = 2;
- chatto.api.v1.ThreadViewerState viewer_state = 3;
- }
- // Removes one room from the client projection.
- message RealtimeProjectionRoomRemove {
- // Stable room ID to remove.
- string room_id = 1;
- }
- // Complete ordered room-group layout visible to the caller.
- message RealtimeProjectionRoomGroupsReplace {
- // Visible room groups in sidebar order.
- repeated chatto.api.v1.RoomGroup groups = 1;
- }
- // Complete retained recent timeline window for one room.
- message RealtimeProjectionRoomTimelineReplace {
- // Room whose retained window is replaced.
- string room_id = 1;
- // Renderable current timeline state. Deleted and crypto-shredded message
- // bodies are absent and represented by their normal tombstone fields.
- chatto.api.v1.RoomTimelinePage page = 2;
- // Opaque cursor for every retained row, keyed by timeline event ID.
- map<string, string> event_cursors = 3;
- }
- // Current renderable state for one timeline event.
- message RealtimeProjectionRoomTimelineEventUpsert {
- // Room containing the event.
- string room_id = 1;
- // Current renderable event state.
- chatto.api.v1.RoomTimelineEvent event = 2;
- // Related users needed to render this event.
- chatto.api.v1.RoomTimelineIncludes includes = 3;
- // Reaction transition that caused this upsert, when applicable.
- optional RealtimeProjectionReactionChange reaction_change = 4;
- // Preserve a deleted echo row as a tombstone. Directly deleted echoes use a
- // remove operation instead; this flag identifies canonical-reply deletion.
- bool retain_deleted_row = 5;
- // Opaque room-timeline cursor for this canonical row. Clients use retained
- // row cursors to advance a bounded window without a separate refresh read.
- string event_cursor = 6;
- }
- // Removes one projection-only row from a retained room timeline.
- message RealtimeProjectionRoomTimelineEventRemove {
- // Room containing the retained row.
- string room_id = 1;
- // Stable event ID of the row to remove.
- string event_id = 2;
- }
- // Finite current notification state emitted on bootstrap and every resume.
- message RealtimeProjectionNotificationOccurrencesReplace {
- // True only when this live replacement should produce a one-shot local
- // notification sound. Bootstrap and reconciliation replacements are false.
- bool play_notification_sound = 1;
- // Authoritative Notifications 2.0 occurrences and exact unread count.
- chatto.api.v1.ListNotificationOccurrencesResponse occurrences = 2;
- }
- // Lightweight current viewer state for one projected room.
- message RealtimeProjectionRoomViewerStateReplace {
- string room_id = 1;
- chatto.api.v1.RoomViewerState viewer_state = 2;
- }
- // Finite current active-call state visible to the caller.
- message RealtimeProjectionActiveCallsReplace {
- repeated chatto.api.v1.ActiveCall calls = 1;
- }
- // Reaction transition retained for integrations while the containing timeline
- // row carries the authoritative aggregate reaction state.
- message RealtimeProjectionReactionChange {
- // Whether the actor added or removed the reaction.
- RealtimeProjectionReactionAction action = 1;
- // Canonical reacted-to message event ID.
- string message_event_id = 2;
- // Reaction emoji.
- string emoji = 3;
- // User who changed the reaction.
- string user_id = 4;
- }
- // Kind of reaction transition.
- enum RealtimeProjectionReactionAction {
- // Unknown action.
- REALTIME_PROJECTION_REACTION_ACTION_UNSPECIFIED = 0;
- // A reaction was added.
- REALTIME_PROJECTION_REACTION_ACTION_ADDED = 1;
- // A reaction was removed.
- REALTIME_PROJECTION_REACTION_ACTION_REMOVED = 2;
- }
- // Application-level ping.
- message RealtimePing {
- // Client-chosen opaque value echoed in the pong.
- string nonce = 1;
- }
- // Application-level pong.
- message RealtimePong {
- // Echo of the ping nonce.
- string nonce = 1;
- }
- // Application-level heartbeat.
- message RealtimeHeartbeat {
- // Stable event ID for this heartbeat.
- string id = 1;
- // Time the heartbeat was emitted.
- google.protobuf.Timestamp created_at = 2;
- }
- // Protocol error.
- message RealtimeError {
- // Stable machine-readable error code.
- string code = 1;
- // Human-readable diagnostic message.
- string message = 2;
- // True when the server will close the socket after sending this error.
- bool fatal = 3;
- // Suggested retry delay for a non-fatal rejected request. Omitted when the
- // request should not be retried automatically.
- optional uint32 retry_after_ms = 4;
- // Room associated with a room-hydration error. Omitted for errors that are
- // not caused by `hydrate_room`.
- optional string room_id = 5;
- }
- // Close instruction sent before the socket is closed when possible.
- message RealtimeClose {
- // Stable machine-readable close code.
- string code = 1;
- // Human-readable diagnostic message.
- string message = 2;
- // True when the client should reconnect after a delay.
- bool reconnect = 3;
- // Suggested reconnect delay in milliseconds.
- uint32 retry_after_ms = 4;
- }
- // One authorized transient event delivered over the realtime WebSocket.
- //
- // These events have no resume cursor and are never replayed. Durable state is
- // delivered only as RealtimeProjectionEvent operations.
- message RealtimeEventEnvelope {
- reserved 4, 5, 10 to 12, 20 to 21, 40 to 47, 60 to 76, 80 to 89;
- reserved "cursor", "resume_cursor", "message_posted", "message_edited", "message_retracted", "reaction_added", "reaction_removed", "room_created", "room_updated", "room_deleted", "room_archived", "room_unarchived", "user_joined_room", "user_left_room", "room_universal_changed", "thread_created", "user_custom_status_set", "user_custom_status_cleared", "asset_processing_started", "asset_processing_succeeded", "asset_processing_failed", "asset_deleted", "notification_created", "notification_dismissed", "notification_level_changed", "thread_follow_changed", "room_marked_as_read", "server_updated", "user_profile_updated", "server_user_preferences_updated", "room_groups_updated", "server_member_deleted", "call_started", "call_participant_joined", "call_participant_left", "call_ended", "mention_notification", "new_direct_message_notification";
- // Stable event ID.
- string id = 1;
- // Time the event was created.
- google.protobuf.Timestamp created_at = 2;
- // User or system actor that caused the event, when known.
- optional string actor_id = 3;
- oneof event {
- // A user is typing in a room or thread that the viewer can read.
- RealtimeTypingEvent user_typing = 30;
- // A user's live presence changed.
- RealtimePresenceChangedEvent presence_changed = 31;
- // The current user's session was terminated.
- RealtimeSessionTerminatedEvent session_terminated = 90;
- }
- }
- // Typing signal.
- //
- // This is an ephemeral signal. The viewer must be a room member. Main-room
- // typing requires `message.read`. Thread typing requires `message.read` or a
- // matching thread relationship with `message.read-interactions`. DM membership
- // authorizes DM typing delivery. `room_id` and `thread_root_event_id` identify
- // where to display typing state; clients normally do not hydrate it.
- message RealtimeTypingEvent {
- // Room where the actor is typing.
- string room_id = 1;
- // Thread root event ID when the actor is typing in a thread.
- optional string thread_root_event_id = 2;
- }
- // Presence-changed signal.
- //
- // The latest presence status is inline. Use `UserService.GetUser` when
- // the surrounding user profile or custom status also needs refreshing.
- message RealtimePresenceChangedEvent {
- // User whose presence changed.
- string user_id = 1;
- // Latest presence status.
- chatto.api.v1.PresenceStatus status = 2;
- }
- // Session-terminated signal for the connected user's current session.
- message RealtimeSessionTerminatedEvent {
- // Termination reason.
- string reason = 1;
- }
|