Rooms and presence
A room is a relay on Velven's servers for 2 to 16 signed-in players of your space: what one sends, the others get at once. You need no server of your own. Presence tells a player's friends what they are playing and lets them join.
Open and join a room
A player opens a room and is its host; others join by its id. A player is in one room at a time, and has at most five rooms open; a room nobody enters within two minutes closes.
const made = await Velven.rooms.create({ visibility: "friends", maxPlayers: 4 });// { ok: true, room: { id, you, host, players, data, maxPlayers, visibility } }const open = await Velven.rooms.list(); // { ok: true, rooms: [{ id, visibility, host, players, maxPlayers, full, createdAt }] }const joined = await Velven.rooms.join(open.rooms[0].id);Velven.rooms.leave();visibilitypublic,friendsorprivateDefaultpublicpublic: listed for every player of the space.friends: listed for the host's friends only, and only they may join.private: never listed; joined through an invite or its link.maxPlayers2 to 16Default8- The most players in the room at once. A join past it answers
full.
Velven.rooms.current is the room the player is in, or null. list() answers full rooms too, marked full, so you can show them.
Invite friends
await Velven.rooms.invite("mara"); // a notification to a friend, with a Join buttonconst link = Velven.rooms.inviteLink(); // your space's Velven page with ?room=<id>, or null outside a room// Velven put the player in a room: they opened an invite link, or pressed Join.Velven.rooms.on("room", ({ room }) => enterLobby(room));Only a player in the room, or the one who opened it, invites. An invite reaches only a friend: friends are mutual, made on Velven. invite answers { ok: true } whether or not the handle is a friend's, so an invite tells your space nothing about who is one. A friend is invited only when they may join: a friends room takes the opener's friends only. The link works for anyone signed in who may join the room. Whoever opens it lands on your space's Velven page and joins the room as the page loads; the room event tells your space, even when you start listening after the join.
Send and receive
Velven.rooms.send({ x: 3, y: 4 }); // JSON to everyone elseVelven.rooms.send(bytes, { to: playerId }); // or bytes (an ArrayBuffer or a typed array), to one playerVelven.rooms.on("message", ({ from, data }) => apply(from, data)); // data: JSON as sent, or an ArrayBufferVelven.rooms.chat("gg"); // to everyone, you includedVelven.rooms.on("chat", (line) => showChat(line.handle, line.text)); // { id, from, handle, text, at, sig }await Velven.rooms.report(line, "harassment"); // a chat line as on("chat") heard it, to Velven's teamsend, chat, setData, kick and leave answer at once: { ok: true }, or { ok: false, error } with not_in_room, not_host, too_large or too_long. A message is 16 KB at most and a chat line 500 characters. The relay signs each chat line (sig), and a report is taken only for a line it signed, so pass the line to report as you heard it.
The host
The player who opens the room is its host whenever they are in it. For the first 30 seconds nobody else hosts, so a player who joins before the opener's page connects does not take it; after that, a room without its opener is hosted by the player in longest. When the host leaves, the player in longest takes over, and the opener takes it back on coming in; everyone hears a host event. Only the host sets the room's data and removes players:
Velven.rooms.setData("round", 2); // null deletes a keyVelven.rooms.on("data", ({ data }) => drawRound(data.round)); // everyone, the host too, hears the whole mapVelven.rooms.kick(playerId); // they cannot come back into this roomRoom data is a map of keys of 1 to 64 characters, 64 KB in all, and a player who joins gets it in current.data. Keep what every player must agree on there, such as the round or the map; send moves as messages.
Events
Velven.rooms.on(event, handler) returns a function that stops listening.
| Event | Payload | When |
|---|---|---|
join | { player, players } | A player came in. player is { id, handle, avatar, joinedAt }. |
leave | { player, reason, players } | A player went; reason is left or kicked, and player their id. |
host | { host } | The host changed; host is a player's id. |
message | { from, data } | Another player sent to everyone, or to you. |
chat | { id, from, handle, text, at, sig } | A chat line, yours included; sig is the relay's signature, which report sends along. |
data | { data } | The host changed the room's data. |
room | { room } | Velven put the player in a room: an invite link or Join. |
close | { reason } | You are out of the room: left, kicked, replaced (you joined again from another tab), full, wrong_space or lost (the connection dropped). |
error | { code, message } | The relay refused something, such as rate_limited, or a join from a link failed. |
- Event
join- Payload
{ player, players }- When
- A player came in.
playeris{ id, handle, avatar, joinedAt }.
- Event
leave- Payload
{ player, reason, players }- When
- A player went;
reasonisleftorkicked, andplayertheir id.
- Event
host- Payload
{ host }- When
- The host changed;
hostis a player's id.
- Event
message- Payload
{ from, data }- When
- Another player sent to everyone, or to you.
- Event
chat- Payload
{ id, from, handle, text, at, sig }- When
- A chat line, yours included;
sigis the relay's signature, whichreportsends along.
- Event
data- Payload
{ data }- When
- The host changed the room's data.
- Event
room- Payload
{ room }- When
- Velven put the player in a room: an invite link or Join.
- Event
close- Payload
{ reason }- When
- You are out of the room:
left,kicked,replaced(you joined again from another tab),full,wrong_spaceorlost(the connection dropped).
- Event
error- Payload
{ code, message }- When
- The relay refused something, such as
rate_limited, or a join from a link failed.
A small example
A page where everyone in a room sees each other's pointer. The first player opens a room and shares the link; whoever opens it joins.
<!doctype html><html><head> <meta name="velven" content="@handle"> <script src="https://velven.ai/sdk/v1.js"></script></head><body> <button id="open">Open a room</button> <p id="status"></p> <canvas id="board" width="640" height="400"></canvas> <script> const status = document.getElementById("status"); const ctx = document.getElementById("board").getContext("2d"); const pointers = new Map(); // player id -> { x, y } function draw() { ctx.clearRect(0, 0, 640, 400); for (const [id, p] of pointers) { ctx.fillStyle = id === Velven.rooms.current?.you.id ? "#fff" : "#888"; ctx.fillRect(p.x - 4, p.y - 4, 8, 8); } } function enter(room) { status.textContent = `In a room with ${room.players.length} of ${room.maxPlayers}. Share: ${Velven.rooms.inviteLink()}`; } document.getElementById("open").onclick = async () => { const made = await Velven.rooms.create({ visibility: "private", maxPlayers: 4 }); if (made.ok) enter(made.room); else if (made.error === "signed_out") await Velven.signIn(); else status.textContent = `Could not open a room: ${made.error}`; }; Velven.rooms.on("room", ({ room }) => enter(room)); Velven.rooms.on("join", () => enter(Velven.rooms.current)); Velven.rooms.on("leave", ({ player }) => { pointers.delete(player); draw(); }); Velven.rooms.on("message", ({ from, data }) => { pointers.set(from, data); draw(); }); let last = 0; document.getElementById("board").onpointermove = (e) => { const room = Velven.rooms.current; if (!room || e.timeStamp - last < 50) return; // 20 a second, under the relay's 30 last = e.timeStamp; const p = { x: e.offsetX, y: e.offsetY }; pointers.set(room.you.id, p); Velven.rooms.send(p); draw(); }; </script></body></html>The status line is set with textContent, so a handle is never read as markup. A real game keeps the same shape: moves as messages, shared state in the room's data, a lobby drawn from current.players.
Test with a friend
On localhost a room holds only you: chat and setData come back to you, send reaches nobody. To test with others, publish a preview and send them its link: anyone signed in who holds it can play there, and rooms opened in a preview stay in your space's sandbox, apart from the live space's rooms.
Limits
- 2 to 16 players a room, and one room at a time per player.
- A message is 16 KB at most, JSON or bytes. A chat line is 1 to 500 characters. Room data is 64 KB in all.
- 30 messages a second per player, with a burst of 60. Past that the relay drops them and sends one
errora second withrate_limited; send state at a steady rate rather than on every frame. - Per account: 30 rooms opened an hour and 30 room passes a minute.
- A room closes 30 seconds after its last player leaves.
Error codes
| Code | Meaning |
|---|---|
in_room, not_in_room | Leave the room first, or join one. |
not_found, closed, full | No such room in this space, it has closed (a room nobody enters within two minutes closes), or it is full. |
friends_only | A friends room of someone who is not your friend. |
kicked | The host removed this player from the room. |
not_host | Only the host sets data or kicks. |
too_large, too_long | A message over 16 KB, or a chat line over 500 characters. |
signed_out, unavailable, banned, rate_limited, failed | As for scores. |
- Code
in_room,not_in_room- Meaning
- Leave the room first, or join one.
- Code
not_found,closed,full- Meaning
- No such room in this space, it has closed (a room nobody enters within two minutes closes), or it is full.
- Code
friends_only- Meaning
- A friends room of someone who is not your friend.
- Code
kicked- Meaning
- The host removed this player from the room.
- Code
not_host- Meaning
- Only the host sets data or kicks.
- Code
too_large,too_long- Meaning
- A message over 16 KB, or a chat line over 500 characters.
- Code
signed_out,unavailable,banned,rate_limited,failed- Meaning
- As for scores.
Presence
While a signed-in player is on your space's Velven page, their friends see “Playing” and your space's name, in their friends list and on the player's profile; once they leave, “Last seen” and when. You need do nothing for this. Only friends see it.
Add a line of your own with presence.set:
await Velven.presence.set({ status: "Level 3, 2 lives left" }); // 80 characters at most; null clears it- When the player is in a room a friend may join, that friend also gets a Join button beside it, which opens your space's page and joins the room.
presence.setanswers{ ok: true }, or{ ok: false, error }withsigned_out,unavailable,too_long,rate_limitedorfailed. Up to 30 lines a minute per account.- Say what the player is doing, not who they are with: friends see the line as it is.