Docs menu

Rooms and membership

A room is a UUID. dotwire has no create-room call: a room exists as soon as you add members to it. A room id that isn't a UUID can't exist, so room-scoped routes answer it with 404.

Membership

The room_members table is the authoritative record of who belongs to which room. The token never grants a room. You manage membership from your host backend through the admin API or a host SDK.

Node.js (@dotwire/host)

await host.setUserRole('user-42', 'member');
await host.addRoomMembers(roomId, ['user-42', 'user-57']);

.NET (Dotwire.Host)

Guid roomId = Guid.Parse("a0000000-0000-0000-0000-000000000001");
await client.SetUserRoleAsync("user-42", "member");
await client.AddRoomMembersAsync(roomId, ["user-42", "user-57"]);

A member also needs a role in user_roles, or the role cross-check denies every request. Adding members can grant the member role at the same time, without overwriting an existing auditor or admin role:

  • The HTTP API does this only when the request sets ensureMemberRole: true. The default is false.
  • Both host SDKs set it by default. To add membership only, pass { ensureMemberRole: false } as the options argument in Node, or the named argument ensureMemberRole: false in .NET.

Deleting a user's role also removes all of that user's room memberships.

Who can read a room

Reading a room follows membership, not role. History, live delivery, SSE, and the participant list all require the caller to be in room_members. An auditor or admin who wants to read a room's messages must be a member like anyone else.

Participants and presence

These answer different questions:

  • Participants are the room's members: GET /rooms/{roomId}/participants returns their user ids, sorted. It returns ids only. Resolve names and avatars through your own user directory.
  • Presence is live connection activity. The server sends coalesced deltas (joined, left) as members connect and disconnect. There is no presence snapshot endpoint, so a client sees only changes that happen after it subscribes.

Refresh the participant list after your host changes membership, or when a user opens the room.

Revocation

When you remove a member, or delete a user's role, dotwire acts on live connections immediately:

  • Every hub connection that user holds in the room receives MembershipRevoked, is removed from the room, and a presence leave is queued.
  • Every SSE stream that user holds for the room receives a revoked event and closes.
  • The revocation travels over NATS core, so it reaches every node, not just the node that handled the admin request.

The SDKs surface it directly: onMembershipRevoked in @dotwire/client, which also unsubscribes the room, status: 'revoked' in @dotwire/react, and a revoked event in both host SDKs' SSE streams.

System messages

An admin can inject a message into any room as an asserted sender, without being a member. This is how hosts post bot replies, notifications, and moderation notices.

  • The sender id defaults to system. dotwire doesn't check that the sender id is a real member.
  • Every injection writes a message.injected audit event. If the event can't be written, dotwire erases the message before anyone receives it and returns 503 audit_unavailable, so you can retry without creating a duplicate.
  • Injecting into a room doesn't grant read access to it.
  • Presend and postsend webhooks skip injected messages unless you turn on IncludeAdminSends.

Node.js (@dotwire/host)

const room = host.room('a0000000-0000-0000-0000-000000000001');
await room.sendSystemMessage('Server restart scheduled in 5 minutes.');

.NET (Dotwire.Host)

var ack = await client.SendSystemMessageAsync(roomId, "system announcement");
var botAck = await client.SendSystemMessageAsync(roomId, "build finished", senderId: "bot:ci");

@dotwire/client routes messages whose sender id is system, or starts with system: or bot:, to its onSystemMessage listeners.

Limits

  • User ids and sender ids are 1 to 256 characters, with no control characters.
  • Message content must be non-empty and at most Dotwire:MaxContentBytes UTF-8 bytes, 65,536 by default.

Related