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.phpforchat.*,client-dynamic.imvu.com/api/xmlrpc/client.phpfortest.*): 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:
test.getRoomCardInfo({room_instance_id}): the room's card (name, owner, flags likeallowApProducts,allowLoadNewRoom). Room instance IDs look like<owner id>-<n>.chat.getOrMakeChat({userId, activity: "publicroom-<room instance id>", chatId: 0, publicroom: true, private}): returnschatId(an integer),seat,lastMessageId,participantUrl,partnerId. A "declined" response carriesreasonandexplanation(shown as an error). A "waiting" response makes the client wait and retry withinviteId.test.getRoomState({room_instance_id}): the scene:room_hash, androom_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.- IMQ: subscribe to
/chat/<chatId>(with propertylisten_to_self: false), theninv:/room/room-<room instance id>(contents not seen yet). - Chat commands sent on joining (below):
*msg SeatAssignment ...,*seat <n>,*resume <userId>(or*accept <userId>). 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"}(to0 for everyone). The client takes the sender from the gateway'suser_id(the last path segment) and flags a mismatch withuserIdas forged. - Mount
control: JSON with acommand, such asPlayActionandPlayCoopAction(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>, androom_occupancy/for counts): results for subscribe operations, joined and left notices, relayingSendMessageto the queue's other members with sequence numbers, catching up onOpenFloodgates, andcontrolmessages. 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 callsgetJoinPublicRoomInfo). - Moving furniture in one's own room (a capture in My Room).
- Private rooms, invitations and "Chat Now" (one-to-one).
- What
G2cCreateMountand state mounts are used for in rooms.