Hosted rooms' instance IDs name their owner: <owner>-<2100000000+n>
CI / test (push) Successful in 58s
CI / test (push) Successful in 58s
The client takes a room's owner from the instance ID's first part; with 2100000000 there, the owner couldn't decorate their hosted room (putting furniture on went to their home room instead). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f65f36c1ea
commit
43183ec7ee
7 files changed
+17
-13
No files matched your search
+1
-1
@@ -227,7 +227,7 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
|
|||||||
|
|
||||||
const hostedView = ({ room: r, ownerName }: { room: typeof schema.hostedRoom.$inferSelect; ownerName: string }) => ({
|
const hostedView = ({ room: r, ownerName }: { room: typeof schema.hostedRoom.$inferSelect; ownerName: string }) => ({
|
||||||
n: r.n,
|
n: r.n,
|
||||||
instanceId: instanceId(r.n),
|
instanceId: instanceId(r.ownerImvuId, r.n),
|
||||||
chatId: chatId(r.n),
|
chatId: chatId(r.n),
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description,
|
description: r.description,
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export const decorationHash = (contents: DecorationItem[]) =>
|
|||||||
function ownerOf(instance: string): number {
|
function ownerOf(instance: string): number {
|
||||||
if (!decorationInstance.test(instance)) throw new DecorationError(400, "invalid", "That isn't an IMVU room.");
|
if (!decorationInstance.test(instance)) throw new DecorationError(400, "invalid", "That isn't an IMVU room.");
|
||||||
const owner = Number(instance.slice(0, instance.indexOf("-")));
|
const owner = Number(instance.slice(0, instance.indexOf("-")));
|
||||||
if (owner >= hostedBase) throw new DecorationError(400, "hosted_room", "Hosted rooms keep their own scene.");
|
if (Number(instance.slice(instance.indexOf("-") + 1)) >= hostedBase)
|
||||||
|
throw new DecorationError(400, "hosted_room", "Hosted rooms keep their own scene.");
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export const maxPlacement = 200;
|
|||||||
export const sceneHash = (roomPid: number, contents: SceneItem[]) =>
|
export const sceneHash = (roomPid: number, contents: SceneItem[]) =>
|
||||||
createHash("sha256").update(JSON.stringify([roomPid, contents])).digest("hex");
|
createHash("sha256").update(JSON.stringify([roomPid, contents])).digest("hex");
|
||||||
|
|
||||||
export const instanceId = (n: number) => `${hostedBase}-${n}`;
|
// "<owner's IMVU ID>-<hostedBase + n>": the client takes a room's owner from the first part (so the owner can
|
||||||
|
// decorate it), and IMVU's own room numbers are far below hostedBase.
|
||||||
|
export const instanceId = (owner: number, n: number) => `${owner}-${hostedBase + n}`;
|
||||||
export const chatId = (n: number) => hostedBase + n;
|
export const chatId = (n: number) => hostedBase + n;
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ test("only the room's owner decorates it; contents are checked", async () => {
|
|||||||
const put = (contents: unknown, instance = room) => call(owner, "PUT", `/api/v1/decorations/${instance}`, { contents });
|
const put = (contents: unknown, instance = room) => call(owner, "PUT", `/api/v1/decorations/${instance}`, { contents });
|
||||||
for (const bad of ["502", "0502-3", "502-0", "502-3-4", "x-1", "1234567890123-1"])
|
for (const bad of ["502", "0502-3", "502-0", "502-3-4", "x-1", "1234567890123-1"])
|
||||||
expect((await put([item], bad)).status).toBe(400);
|
expect((await put([item], bad)).status).toBe(400);
|
||||||
expect((await put([item], "2100000000-5")).json.error).toBe("hosted_room");
|
expect((await put([item], "12345-2100000005")).json.error).toBe("hosted_room");
|
||||||
expect((await put([[7, "AAAAAAAAAA", ""]])).status).toBe(400);
|
expect((await put([[7, "AAAAAAAAAA", ""]])).status).toBe(400);
|
||||||
expect((await put([[7, 12345, ""]])).status).toBe(400);
|
expect((await put([[7, 12345, ""]])).status).toBe(400);
|
||||||
expect((await put([[-1, "aaaaaaaaaa", ""]])).status).toBe(400);
|
expect((await put([[-1, "aaaaaaaaaa", ""]])).status).toBe(400);
|
||||||
@@ -157,7 +157,7 @@ test("viewers see the products they may, with a hash of what they see", async ()
|
|||||||
// Unknown rooms have nothing.
|
// Unknown rooms have nothing.
|
||||||
expect((await call(undefined, "GET", "/api/v1/decorations/601-9")).json).toEqual({ instance: "601-9", contents: [], hash: hash([]) });
|
expect((await call(undefined, "GET", "/api/v1/decorations/601-9")).json).toEqual({ instance: "601-9", contents: [], hash: hash([]) });
|
||||||
expect((await call(undefined, "GET", "/api/v1/decorations/601-x")).status).toBe(400);
|
expect((await call(undefined, "GET", "/api/v1/decorations/601-x")).status).toBe(400);
|
||||||
expect((await call(undefined, "GET", "/api/v1/decorations/2100000000-1")).status).toBe(400);
|
expect((await call(undefined, "GET", "/api/v1/decorations/12345-2100000001")).status).toBe(400);
|
||||||
|
|
||||||
const put = await call(owner, "PUT", `/api/v1/decorations/${room}`, { contents: [g, a, r] });
|
const put = await call(owner, "PUT", `/api/v1/decorations/${room}`, { contents: [g, a, r] });
|
||||||
expect(put.status).toBe(200);
|
expect(put.status).toBe(200);
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ test("rooms are made, listed, changed and closed by their owners", async () => {
|
|||||||
expect(made.status).toBe(201);
|
expect(made.status).toBe(201);
|
||||||
const n = made.json.room.n;
|
const n = made.json.room.n;
|
||||||
expect(made.json.room).toMatchObject({
|
expect(made.json.room).toMatchObject({
|
||||||
instanceId: `2100000000-${n}`,
|
instanceId: `302-${2_100_000_000 + n}`,
|
||||||
chatId: 2_100_000_000 + n,
|
chatId: 2_100_000_000 + n,
|
||||||
name: "The Lounge",
|
name: "The Lounge",
|
||||||
description: "A quiet place.",
|
description: "A quiet place.",
|
||||||
|
|||||||
+2
-2
@@ -65,12 +65,12 @@ the `GET`s also signed out.
|
|||||||
| `DELETE /api/v1/hosted-rooms/:n` | (owner or moderator) closes it for good; everyone in it gets `room.closed` |
|
| `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,
|
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.
|
createdAt}`; `instanceId` is `<owner IMVU ID>-<2100000000 + n>`, `chatId` 2100000000 + n, `occupancy` the people in it now.
|
||||||
|
|
||||||
## Decorations
|
## Decorations
|
||||||
|
|
||||||
T5 products placed in ordinary IMVU rooms (docs/products.md, "Decorations"). `instance` is IMVU's room instance,
|
T5 products placed in ordinary IMVU rooms (docs/products.md, "Decorations"). `instance` is IMVU's room instance,
|
||||||
`<owner's customer ID>-<n>` (`^[1-9][0-9]{0,11}-[1-9][0-9]{0,9}$`); hosted rooms' (`2100000000-<n>`) are refused
|
`<owner's customer ID>-<n>` (`^[1-9][0-9]{0,11}-[1-9][0-9]{0,9}$`); hosted rooms' (room number 2100000000 and up) are refused
|
||||||
(400 `hosted_room`: they have their own scene). A room's decorations are `{instance, contents, hash}`: `contents`
|
(400 `hosted_room`: they have their own scene). A room's decorations are `{instance, contents, hash}`: `contents`
|
||||||
is a list of `[furniture instance ID (integer), hub product ID, placement (up to 200 characters)]`, `hash` the
|
is a list of `[furniture instance ID (integer), hub product ID, placement (up to 200 characters)]`, `hash` the
|
||||||
sha256 (hex) of `contents` as JSON.
|
sha256 (hex) of `contents` as JSON.
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ owner's furniture), occupancy in the room list. Later: moderators, bans and boot
|
|||||||
|
|
||||||
IDs IMVU won't use, so the proxy can tell hosted rooms apart:
|
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
|
- A room has a number `n` (1, 2, ...), its **instance ID** is `<owner's IMVU ID>-<2100000000 + n>` and its
|
||||||
`2100000000 + n`. (IMVU's chat IDs were about 1,390,000,000 in 2026-10, and XML-RPC integers end at
|
**chat ID** is `2100000000 + n`. The client takes a room's owner from the instance ID's first part (so the owner
|
||||||
|
can decorate it); IMVU's own room numbers are small. (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.)
|
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
|
- The room shell is an IMVU product (`roomPid`), chosen when the room is made (any room product; the client loads
|
||||||
it like any room).
|
it like any room).
|
||||||
@@ -71,12 +72,12 @@ stored (design.md: chat retention).
|
|||||||
## The proxy's part
|
## The proxy's part
|
||||||
|
|
||||||
- **XML-RPC** for hosted IDs, answered by the proxy (never sent to IMVU): `test.getRoomCardInfo`,
|
- **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`,
|
`chat.getOrMakeChat` (activity `publicroom-<owner>-<2100000000+n>`: joins, answers `chatId`, `seat`, `lastMessageId`,
|
||||||
`participantUrl`), `test.getRoomState` (the scene, with community products as local IDs), `chat.getParticipants`,
|
`participantUrl`), `test.getRoomState` (the scene, with community products as local IDs), `chat.getParticipants`,
|
||||||
`chat.terminateChat`, `test.setRoomState` (owners: saved with `PUT .../scene`).
|
`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`,
|
- **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
|
`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
|
`room.message` comes back as `G2cSendMessage`. `inv:/room/room-<owner>-<2100000000+n>` is answered with a result and a
|
||||||
`node` mount, and nothing more.
|
`node` mount, and nothing more.
|
||||||
- **Room list**: hosted rooms are added to `rooms_list_paginated.php`'s answer, and `room_occupancy/2100000000-<n>`
|
- **Room list**: hosted rooms are added to `rooms_list_paginated.php`'s answer, and `room_occupancy/<owner>-<2100000000+n>`
|
||||||
queues are answered from `room.participants`.
|
queues are answered from `room.participants`.
|
||||||
Reference in new issue
Block a user