Rooms made on the website (hosted_room, migration 0005), the REST routes in docs/hosted-rooms.md, and the realtime room.* protocol: joining with the capacity, adult and linked-account rules, seats, numbered messages with forged senders dropped and a rate limit, closing. The Rooms page lists them, with a form to make one and the owner's edit and close. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0c67232425
commit
2a1fc94df6
18 files changed
+3138
-52
No files matched your search
+18
-1
@@ -30,7 +30,7 @@ is. Codes: `invalid`, `signed_out`, `forbidden`, `not_found`, `no_such_imvu_acco
|
||||
| `POST /api/auth/device/token` | `{grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code, client_id}` → `{access_token}` (a session token, used as a bearer token) or `{error: authorization_pending \| slow_down \| expired_token \| access_denied}` |
|
||||
| `POST /api/v1/device/approve` | (website, signed in) `{code}`: signs the waiting proxy in as this person |
|
||||
| `POST /api/v1/badges` | (bearer or website session) `{imvuIds}` (at most 200 IMVU account IDs) → `{badges}`: those linked to a T5DE account that turned on `showBadge` for them and isn't banned. Nothing else about them. 120 a minute per person. |
|
||||
| `GET /api/v1/realtime` | WebSocket, bearer token: presence, outfits, and the room channel. Protocol in `apps/api/src/realtime.ts`. |
|
||||
| `GET /api/v1/realtime` | WebSocket, bearer token: presence, outfits, the room channel and hosted rooms (`room.*`, docs/hosted-rooms.md). Protocol in `apps/api/src/realtime.ts`. |
|
||||
| `GET /.well-known/jwks.json` | the Ed25519 keys JWTs are signed with |
|
||||
|
||||
## Website routes (signed in unless noted)
|
||||
@@ -50,6 +50,23 @@ is. Codes: `invalid`, `signed_out`, `forbidden`, `not_found`, `no_such_imvu_acco
|
||||
| `DELETE /api/v1/account/links/:imvu` | unlink |
|
||||
| `POST /api/v1/account/signout-computers` | signs T5DE out on all their computers |
|
||||
|
||||
## Hosted rooms
|
||||
|
||||
Rooms the hub hosts (docs/hosted-rooms.md, which has the details and the realtime part). Bearer or website session;
|
||||
the `GET`s also signed out.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `POST /api/v1/hosted-rooms` | `{name, description?, roomPid, rating, maxUsers?, imvuId}` → 201 `{room}`. `imvuId`: one of the person's linked accounts, shown as owner. Name 1-64 characters, description up to 512 (plain text), `maxUsers` 2-30 (default 10), `rating` `general` or `adult` (adult people only). Up to 10 open rooms a person (`room_cap`), 20 new ones an hour. |
|
||||
| `GET /api/v1/hosted-rooms` | `{rooms}`: open rooms, newest first (at most 200); adult ones for adults only. `?mine=1`: the person's own. |
|
||||
| `GET /api/v1/hosted-rooms/:n` | `{room, scene: {contents, hash}}`; 404, or 410 `closed` |
|
||||
| `PATCH /api/v1/hosted-rooms/:n` | (owner) `{name?, description?, rating?, maxUsers?, roomPid?}` → `{room}`. Made adult: non-adults in it are sent out. |
|
||||
| `PUT /api/v1/hosted-rooms/:n/scene` | (owner) `{contents, roomPid?}` → `{room, scene}`. `contents`: up to 500 `[furniture ID, product ID (integer or hub ID), placement (up to 200 characters)]`. 60 a minute. |
|
||||
| `DELETE /api/v1/hosted-rooms/:n` | (owner or moderator) closes it for good; everyone in it gets `room.closed` |
|
||||
|
||||
A room is `{n, instanceId, chatId, name, description, roomPid, rating, maxUsers, ownerImvuId, ownerName, occupancy,
|
||||
createdAt}`; `instanceId` is `2100000000-<n>`, `chatId` 2100000000 + n, `occupancy` the people in it now.
|
||||
|
||||
## Products, catalogs and libraries
|
||||
|
||||
The routes and tokens are in docs/products.md. Products are `{id, name, description, rating, latest, creator,
|
||||
|
||||
+3
-1
@@ -411,7 +411,9 @@ The proxy streams and adds no waiting.
|
||||
- Phase 1 (local packs) done: t5de-api, t5de-proxy `internal/products`.
|
||||
- Phase 2 (hub identity) under way: accounts, tagline linking, age (vendor-neutral, IMVU's re-checked daily), link
|
||||
caps, device sign-in, presence and badge, console, the room channel and the rooms list. Not yet: a sign-in button
|
||||
in the client (the proxy's `signin` command for now), deployment, hosted rooms. The room channel is tested with
|
||||
in the client (the proxy's `signin` command for now), deployment.
|
||||
- Hosted rooms (phase 5, docs/hosted-rooms.md): the hub's side is done (rooms made on the website, REST, the
|
||||
realtime `room.*` protocol, tested with fake proxies); the proxy's side is under way. The room channel is tested with
|
||||
fake proxies and one live client; two live clients need a second throwaway account.
|
||||
- **Stack changed** (2026-10-29, the user's choice): the hub is a Bun monorepo: Elysia API, Better Auth, Drizzle on
|
||||
Postgres, Next.js website with Tailwind and shadcn/ui (README). It replaces the first version, in Go. Where this
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# T5DE-hosted rooms (phase 1)
|
||||
|
||||
A hosted room lives on the hub (design.md, Rooms): T5DE users find it in the client's room list, join it, sit and
|
||||
chat, and its owner decorates it. The client treats it as an ordinary IMVU room: T5DE's proxy answers the client's
|
||||
room calls and runs the room's IMQ queue against the hub (imq-rooms.md has what the client expects). IMVU never
|
||||
hears of hosted rooms.
|
||||
|
||||
Phase 1: rooms created on the website; joining, seats, chat (`messages` and `control` mounts), the scene (the
|
||||
owner's furniture), occupancy in the room list. Later: moderators, bans and boots, invitations, private rooms.
|
||||
|
||||
## IDs
|
||||
|
||||
IDs IMVU won't use, so the proxy can tell hosted rooms apart:
|
||||
|
||||
- A room has a number `n` (1, 2, ...), its **instance ID** is `2100000000-<n>` and its **chat ID** is
|
||||
`2100000000 + n`. (IMVU's chat IDs were about 1,390,000,000 in 2026-10, and XML-RPC integers end at
|
||||
2,147,483,647; IMVU customer IDs, the first part of its instance IDs, are far below 2,100,000,000.)
|
||||
- The room shell is an IMVU product (`roomPid`), chosen when the room is made (any room product; the client loads
|
||||
it like any room).
|
||||
|
||||
## REST (bearer or cookie session)
|
||||
|
||||
| Route | |
|
||||
| --- | --- |
|
||||
| `POST /api/v1/hosted-rooms` | `{name, description, roomPid, rating, maxUsers, imvuId}`: make a room. `imvuId` is one of the person's linked IMVU accounts, shown as the owner. `rating` `general` or `adult` (adult needs an adult person). `maxUsers` 2-30, default 10. Name 1-64 characters, description up to 512. A person may own up to 10 open rooms. |
|
||||
| `GET /api/v1/hosted-rooms` | (also signed out) `{rooms: [room]}`, open rooms, newest first; adult rooms only for adults. `?mine=1`: the person's own. |
|
||||
| `GET /api/v1/hosted-rooms/:n` | (also signed out) `{room, scene: {contents, hash}}`; 404 `not_found` (or adult, for others), 410 `closed`. |
|
||||
| `PATCH /api/v1/hosted-rooms/:n` | owner: `{name?, description?, rating?, maxUsers?, roomPid?}`. |
|
||||
| `PUT /api/v1/hosted-rooms/:n/scene` | owner: `{contents, roomPid?}`, from the client's `test.setRoomState`. |
|
||||
| `DELETE /api/v1/hosted-rooms/:n` | owner (or moderator console): closes it; everyone in it is told (`room.closed`). |
|
||||
|
||||
A room: `{n, instanceId, chatId, name, description, roomPid, rating, maxUsers, ownerImvuId, ownerName, occupancy,
|
||||
createdAt}` (`ownerName`: the owner's IMVU name as linked; `occupancy`: people in it now). A room whose owner account
|
||||
is no longer linked to its owner (unlinked, or moved to another person), or whose owner is banned, is hidden (not
|
||||
listed, `not_found`) until it's linked again. Closed rooms stay closed; their numbers aren't reused.
|
||||
|
||||
The scene's `contents` is the client's `room_contents`: a list of `[furniture instance ID (integer), product ID
|
||||
(integer or a hub product ID string), placement (string)]`, up to 500 items, placement up to 200 characters. `hash`
|
||||
is a hash of the stored scene (the client caches scenes by it): sha256, hex, of `[roomPid, contents]` as JSON.
|
||||
|
||||
## Realtime (the proxy's WebSocket)
|
||||
|
||||
From the proxy (the session's IMVU account, sent with `session`, must be linked to the person):
|
||||
|
||||
- `{"type": "room.join", "room": n}`: join. Answer `{"type": "room.joined", "room": n, "chat": chatId,
|
||||
"participants": [{"imvu_id", "seat"}], "sequence": s}` (the room's latest message number), or
|
||||
`{"type": "room.error", "room": n, "reason": "full" | "closed" | "adult" | "not_found" | "not_linked" | "too_many",
|
||||
"message": "..."}` (`too_many`: the session is in 5 hosted rooms already). Joining again (a reconnect) answers the
|
||||
same. A newcomer's seat is 0 until it sends `room.seat`. `maxUsers` counts IMVU accounts, so the same account
|
||||
joining from a second session isn't refused as full.
|
||||
- `{"type": "room.leave", "room": n}`.
|
||||
- `{"type": "room.seat", "room": n, "seat": k}`: the person sat down (`*seat k`); 0-99.
|
||||
- `{"type": "room.send", "room": n, "mount": "messages" | "control", "message": "..."}` (up to 64 KB): to everyone
|
||||
else in the room. For `messages` the hub checks the JSON's `userId` is the sender's IMVU account and drops it
|
||||
otherwise (silently). Each session may send bursts of 20, then 10 a second; more is dropped. Malformed messages
|
||||
(bad room, seat, mount, a message over 64 KB, sending to a room it isn't in) get `{"type": "error", "message"}`.
|
||||
|
||||
From the hub:
|
||||
|
||||
- `{"type": "room.message", "room": n, "from": imvuId, "mount": "...", "message": "...", "sequence": s}`: numbered
|
||||
per room, from 1.
|
||||
- `{"type": "room.participants", "room": n, "participants": [{"imvu_id", "seat"}]}`: whenever someone joins, leaves
|
||||
or sits (not to the one joining: `room.joined` has the list).
|
||||
- `{"type": "room.closed", "room": n}`: the room closed, or this session is out of it: its IMVU account was unlinked
|
||||
or it sent `session` with another account, or the room became adult and its account isn't. (A lower `maxUsers`
|
||||
sends no one out; it only stops newcomers.)
|
||||
|
||||
A session closing leaves its rooms. The hub keeps the participants and message numbers in memory; messages aren't
|
||||
stored (design.md: chat retention).
|
||||
|
||||
## The proxy's part
|
||||
|
||||
- **XML-RPC** for hosted IDs, answered by the proxy (never sent to IMVU): `test.getRoomCardInfo`,
|
||||
`chat.getOrMakeChat` (activity `publicroom-2100000000-<n>`: joins, answers `chatId`, `seat`, `lastMessageId`,
|
||||
`participantUrl`), `test.getRoomState` (the scene, with community products as local IDs), `chat.getParticipants`,
|
||||
`chat.terminateChat`, `test.setRoomState` (owners: saved with `PUT .../scene`).
|
||||
- **IMQ**: subscribing to `/chat/<chatId>` of a hosted room is answered by the proxy (result, `JoinedQueue`,
|
||||
`CreateMount`s for `control`, `messages`, `participants`); `SendMessage` on it goes to the hub as `room.send`, and
|
||||
`room.message` comes back as `G2cSendMessage`. `inv:/room/room-2100000000-<n>` is answered with a result and a
|
||||
`node` mount, and nothing more.
|
||||
- **Room list**: hosted rooms are added to `rooms_list_paginated.php`'s answer, and `room_occupancy/2100000000-<n>`
|
||||
queues are answered from `room.participants`.
|
||||
Reference in new issue
Block a user