Add to existing product

Adding multiplayer with Room Service involves two steps:

  1. (10 minutes) Create an Auth Webhook: a POST-request-accepting HTTP endpoint on your server.
  2. (5 minutes) Install the browser SDK and start building!

Creating an Auth Webhook

Room Service uses your auth setup. To do that, we ask you to create an endpoint that accepts POST requests on your server.

A diagram showing Room Service's servers, your servers, and the user's browser in a triangle. The browser sends a message to your server, which forwards it to Room Service's servers, along with an API key. Room Service's servers respond back to your servers with a short-lived token, which you then pass back to the browser. Then the browser opens a WebSocket connection to Room Service's servers.

By default, this endpoint receives a POST body from the browser SDK containing resources the user would like to access. To authorize the user, we'll forward the message along with our API key and a user id to Room Service via the /provision endpoint.

The following is an example in Node.js/Express, but you can build your route in any language:

1const express = require("express");
2const fetch = require("node-fetch");
3const cors = require("cors");
4const app = express();
5const port = 8080;
6app.use(express.json());
7app.use(cors());
8
9// Replace this with your authorization scheme.
10function isLoggedIn(req) {
11 return true; // for the moment, we'll just let everyone in
12}
13
14const API_KEY = "___APIKEY___";
15
16app.post("/my-roomservice", async (req, res) => {
17 if (!isLoggedIn(req)) {
18 return res.send(401);
19 }
20
21 // In practice, this should be whatever user id YOU use.
22 const user = Math.random().toString(36).substr(2, 9);
23 const body = req.body;
24 const r = await fetch("https://super.roomservice.dev/provision", {
25 method: "POST",
26 headers: {
27 Authorization: `Bearer: ${API_KEY}`,
28 "Content-Type": "application/json",
29 },
30
31 body: JSON.stringify({
32 user: user,
33 resources: body.resources,
34 }),
35 });
36
37 return res.json(await r.json());
38});
39
40app.listen(port, () => {
41 console.log(`Example app listening at http://localhost:${port}`);
42});
43

Setting up the browser SDK

Room Service comes with a ~4kb browser library written in TypeScript. Install it with:

1yarn add @roomservice/browser
2
3// OR
4
5npm install --save @roomservice/browser

To start, import the SDK and create a new client that points to your Auth Webhook.

1import { RoomService } from '@roomservice/browser';
2
3const service = new RoomService({
4 auth: '/my-roomservice',
5});

Creating your first room

The key concept of Room Service is the "room". Rooms are like shared folders that hold all your maps, lists, and presence keys inside. If you update something within a room, everyone who's connected to the room will receive the update.

You might create a room for a team, a document, a page, or anything else you'd like.

1const room = await service.room('nasa');

Working with lists

Room Service comes with several data structures that make updates locally-first, so your app is as fast as possible. Each data structure manages conflicts between users automatically, but this is most obvious in lists.

To create a list, grab one from the room:

1let list = room.list("todos")

Lists (and maps) are immutable, so every change returns a new copy of the list:

1list = list.push("water plants")
2list = list.insertAfter(1, "feed cats")
3list = list.set(1, "feed dogs")

Anytime you make a change to something in Room Service, we'll automatically tell everyone else connected to the room. To get updates, we just have to subscribe to them.

1room.subscribe(list, (nextList) => {
2 // nextList is a new list with the changes applied!
3})

To get data out of the list, we can either convert the whole thing (fast) or get an item directly (faster):

1let todos = list.toArray() // ["water plants", "feed dogs"]
2let first = list.get(0) // "water plants"

And when you need to, you can delete anything from the list:

1list = list.delete(0)

Conflicts are managed automatically

Say Alice deletes the 2nd item in a list, at the same time Bob updates the 3rd item. What happens?

1// Initial list looks like
2// ["cats", "birds", "dogs", "snakes", "fish"]
3
4// Alice
5list.delete(2)
6
7// Bob
8list.set(3, "horses")
9
10// ...After network communication
11// ???

In a normal array, this is undefined behavior. If Alice's network is just a bit faster and she goes first, then Bob is changing "fish" into "horses", which he probably didn't want to do.

In Room Service, this just-works™ the way you'd want it to. Regardless of who's arrives first:

  • Bob will change "snakes" into "horses"
  • Alice will delete "dogs"

This works because Room Service's maps and lists aren't true objects and arrays. They're distributed data structures (CRDT-likes) meant for building multiplayer, with similar interfaces.

Working with maps

Much like lists, Room Service also comes with optimistically updating maps. To create one, just use the room:

1let map = room.map("document")

Like lists, maps are immutable; their update functions return a copy of themselves.

1map = map.set("title", "Cool doc 123")
2map = map.delete("body")

To get updates on maps, just subscribe to them with the room.

1room.subscribe(map, (nextMap) => {
2 // nextMap is a new map with the changes applied!
3})

To get data out of the map, use get.

1map.get("title") // "Cool doc 123"