Files
t5de-hub/docs/imq-rooms.md
T
2026-10-29 02:10:42 -07:00

7.1 KiB

What a room needs from the server (client 554.8)

Findings for T5DE-hosted rooms (design.md): what the client asks and expects when it joins and uses a chat room, so the hub (through the proxy) can play IMVU's part for rooms it hosts. From the client's code and a capture of the test account joining an empty public room, chatting once and leaving (2026-10-29). Our own description; IDs are placeholders.

Two channels

A room uses both of the client's connections to IMVU:

  • XML-RPC (chat.imvu.com/api/xmlrpc/chat.php for chat.*, client-dynamic.imvu.com/api/xmlrpc/client.php for test.*): joining, the room's scene, the participant list, leaving.
  • IMQ, the real-time connection (one per client, through the proxy as plaintext): everything said and done in the room, as messages on a queue per chat.

Joining a room

In order, as captured:

  1. test.getRoomCardInfo({room_instance_id}): the room's card (name, owner, flags like allowApProducts, allowLoadNewRoom). Room instance IDs look like <owner id>-<n>.
  2. chat.getOrMakeChat({userId, activity: "publicroom-<room instance id>", chatId: 0, publicroom: true, private}): returns chatId (an integer), seat, lastMessageId, participantUrl, partnerId. A "declined" response carries reason and explanation (shown as an error). A "waiting" response makes the client wait and retry with inviteId.
  3. test.getRoomState({room_instance_id}): the scene: room_hash, and room_state.room_contents, a list of [furniture instance ID, product ID, placement], where placement is a string of numbers (position, rotation, scale, parent slot...). The client caches it by hash.
  4. IMQ: subscribe to /chat/<chatId> (with property listen_to_self: false), then inv:/room/room-<room instance id> (contents not seen yet).
  5. Chat commands sent on joining (below): *msg SeatAssignment ..., *seat <n>, *resume <userId> (or *accept <userId>).
  6. chat.getParticipants({chatId, userId}), then again about every 15 seconds: [{userId, seat, ...}]. People join and leave in the client when they appear in or drop out of this list; messages from people not in it are ignored.

Leaving: chat.terminateChat(userId, chatId). Booting: chat.bootOutOfChat({userId, ...}). Invitations: chat.attemptInvite, acceptInvite, declineInvite, leaveInviteAwayNote. Also seen: test.getBuddyState, test.updateAvatarDownloadSize.

The room list subscribes to room_occupancy/<room instance id> for each room shown (mount occupants), for live occupancy counts.

IMQ

Frames are protobuf Framing{type, data} back to back (t5de-proxy internal/imq). Message types and fields, as the client defines them (field numbers in brackets):

Type Fields
C2gConnect 1302 version [1], user_id [2] (string), cookie [3] (bytes, from login), op_id [4], metadata [5] (key, value)
G2cChallenge 3103 version [1], challenge [2]
C2gChallengeResponse 1303 op_id [1], response [2] (from the challenge and an auth token from login)
G2cResult 3102 op_id [1], status [2], error_message [3]: the answer to any operation with an op_id
C2gOpenFloodgates 1306 state [1]: (queue [1], got_sequence [2], op_id [3]) per queue, to resume after reconnecting
C2gPing 1301 / G2cPong 3101 empty; the client pings every 20 s
C2gSubscribe 1309 / C2gUnsubscribe 1308 queues [1], queues_with_results [2]: (name [1], op_id [2])
G2cJoinedQueue 3106 / G2cLeftQueue 3107 user_id [1], queue [2], sequence [3]: someone joined or left a queue
G2cCreateMount 3108 queue [1], mount [2], type [3] (1 message, 2 state), properties [4], sequence [5]
C2gSendMessage 1304 op_id [1], queue [2], mount [3], message [4] (bytes)
G2cSendMessage 3104 user_id [1] (bytes), queue [2], mount [3], message [4], sequence [5]
C2gStateChange 1305 op_id [1], queue [2], mount [3], properties [4]: (key [1], value [2])
G2cStateChange 3105 op_id [1], user_id [2], queue [3], mount [4], properties [5], sequence [6]
C2gSuicide 1307 / G2cConnectionClosed 3109 closing

Each queue's messages are numbered (sequence), which the client sends back in OpenFloodgates to catch up after a reconnect.

On /chat/<chatId>:

  • Mount messages: JSON {"userId", "chatId", "message", "to"} (to 0 for everyone). The client takes the sender from the gateway's user_id (the last path segment) and flags a mismatch with userId as forged.
  • Mount control: JSON with a command, such as PlayAction and PlayCoopAction (actions on avatars and furniture).

Chat commands

Chat text starting with * is a command (handled in the client; others see the effect, not the text). Seen or handled:

  • Presence and seats: *seat <n>, *resume <id>, *accept <id>, *uid, *boot, *msg SeatAssignment <...>.
  • Outfit: *use <ids...> (the whole outfit, also re-sent about every 30 s), *remove, *putOn, *putOnOutfit, *takeOff, *removeMood, *saveOutfit, *undo, *redo.
  • Typing: *msg BeginText <...> before a line of chat.
  • Scene: *imvu:setRoomState <encoded scene> (from the room's owner; others ignore it from anyone else), *imvu:changeRoom, *imvu:trigger / *imvu:untrigger, *imvu:activateMusic / *imvu:deactivateMusic.
  • Other: *imvu:gift, *imvu:showGift, *imvu:purchase, *imvu:try, *imvu:goto, *imvu:recommend, *imvu:isPureUser, *imvu:txnBoot, *snap, *hiResSnap, *uploadSnap.

Saving a scene: the owner's client calls test.setRoomState({room_instance_id, room_contents, ...}).

What the hub must do for a hosted room

Through the proxy, which sees both channels:

  • XML-RPC: answer getRoomCardInfo, getOrMakeChat, getRoomState, getParticipants, terminateChat, bootOutOfChat, setRoomState (owners), and the invitation calls, for hosted rooms' IDs. These go to the hub, never to IMVU.
  • IMQ: be the gateway for hosted rooms' queues (/chat/<chatId>, and room_occupancy/ for counts): results for subscribe operations, joined and left notices, relaying SendMessage to the queue's other members with sequence numbers, catching up on OpenFloodgates, and control messages. The proxy splits the one IMQ connection by queue: hosted rooms' queues to the hub, the rest to IMVU. It answers operations on hub queues itself (keeping the client's op IDs) and never forwards them.
  • IDs: hosted rooms need room instance IDs and chat IDs IMVU will never use, so the proxy can tell them apart (to be chosen: a reserved range, as for products).
  • Room list: hosted rooms must appear in the client's room list, which comes from a web API (not looked at yet).

The hub keeps the authoritative participant list, seats, scene and the room's moderation (owner, moderators, bans), and checks every message's sender itself.

Not yet looked at

  • What's sent on inv:/room/room-<id>.
  • The room list's API, and joining by link (imvu:room/..., which calls getJoinPublicRoomInfo).
  • Moving furniture in one's own room (a capture in My Room).
  • Private rooms, invitations and "Chat Now" (one-to-one).
  • What G2cCreateMount and state mounts are used for in rooms.