119 lines
7.1 KiB
Markdown
119 lines
7.1 KiB
Markdown
# 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.
|