Using Room Service on the Browser

The low-level browser client lets you use Room Service with any framework, or in just pure JavaScript. If you're using React, you may want to use the React-specific library instead, which comes with some extra goodies.

Installing

You can get the Browser client with:

yarn add @roomservice/browser

# Or...

npm install --save @roomservice/browser

Setting up

To start with, create a RoomService client. In most cases, you should only ever have one instance of the RoomService client.

import RoomService from "@roomservice/browser";

async function authCheck({ rooom }) {
  // We'll get to this in a moment.
}

const rs = new RoomService({
  auth: authCheck,
});

export default rs;

In order to check if a user is allowed to access something in Room Service, the client asks you to pass in a function that will be called when the user first connects.

This function should make a request to your "Auth Webhook" and return the result. You can learn more about what that looks like in the How to Add Authentication guide. But for the moment, let's add something simple:

async function authCheck({ room }) {
  const response = await fetch("http://localhost:8080/api/roomservice", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    credentials: "include",
    body: JSON.stringify({
      room: params.room,
    }),
  });

  if (response.status === 401) {
    throw new Error("Unauthorized!");
  }

  const body = await response.json();
  return body;
}

Connecting to a room

Everything in Room Service lives in a room. If you change something in a room, every other user in that room will be able to see it.

Let's connect to one now:

const room = await rs.room("coolroom");

This room will run the auth check and potentially throw an error! So you may want to try/catch it:

let room;
try {
  room = await rs.room("coolroom");
} catch (err) {
  console.error(err);
}

Using a map

Maps are the simplest data structure in Room Service. You can think of Maps as a distributed hashmap; if you make a change to one, it will be applied locally first and sent over the network separately. That means maps are fast. Really fast.

let map = room.map("java-beach-cafe");

map.toObject(); // { "label": "Java Beach" }

If you make a change to the map, it'll return a new copy of the map with the data applied, like other immutable libraries.

let nextMap = map.set("key", "value");

map.get("key"); // undefined
nextMap.get("key"); // "value"

Every change you make to a map is automatically sent to any other browser that's subscribed to changes on that map:

let coolMap = room.map("coolMap");

room.subscribe(coolMap, (obj) => {
  obj.key; // "value"
});

Objects as values

You can put anything that can convert to JSON in as the value of the map, including javascript objects. For example:

map.set("frame", {
  id: "frame_123",
  background: "red",
  tags: ["cool", "tags"],
});

The resulting scene will then look like this:

{
  frame: {
    id: "frame_123",
    background: "red",
    tags: ["cool", "tags"]
  }
}

In this case, the entire object will be overwritten when you change "frame". If you want property level updates, you'll need to use different keys.

Embedding things inside of maps

If you want to embed maps inside of maps, just store a string key for another map. For example:

const outerMap = room.map("outer-id");
const innerMap = room.map("inner-id");

outerMap.set("inner", "inner-id");

However, we recommend sticking to flatter hierarchies when possible. Flatter hierarchies tend to be eaiser to reason about in a multi-user enviornment that supports both property-level updates and overwriting.

Using a list

If you care about the order of items, use a list. Much like maps, lists allow users to make changes in parallel without needing to wait on a server to coordinate each and every change. Changes are made fist on the browser and then sent over the network.

const list = room.list("coolList");

When you modify the list, you'll update the pure javascript array and send an update to everyone else in the room:

// Insert a number of items at the end of the list
list.push("cat", "two", "three");

// Insert an item after an index
list.insertAfter(2, "four");

// Insert an item at an index, shifting
// other items if needed.
list.insertAt(0, "zero");

// Delete an item at an index
list.delete(3);

// Update an item at an index
list.set(0, "root");

Just like maps, you can listen to updates for lists with the subscribe function:

room.subscribe(list, (arr) => {
  arr[0]; // "zero"
});

Conflict resolution without race conditions

Room Service's lists save you from many different classes of concurrency bugs while preserving the intent of the user.

For example, let's walk through one of these bugs that happen in a normal list. Say we had a list like this:

["dog", "cat", "bird", "bat"];

And two users on seperate browsers try to make modifications to the list at the same time. Alice tries to change the 2nd item.

list[2] = "mouse"; // Alice changes the 2nd item

And Berrry tries to delete the first item.

delete list[0]; // Berry deletes the 0th item

If Berry goes first, then the 2nd item is "bat", not "bird", and Alice's change ends up affecting the wrong item.

But in a Room Service list, both of these changes succeed. Berry can delete the first item in the list:

list.delete(0);

Alice can modify the second item:

list.set(2, "mouse");

And the result is what you'd expect. Both users succeed at their intent. Alice changes "bat" to "mouse", Berry deletes the first item.

["cat", "bird", "mouse"];

Using Presence

Room Service also comes Presence, an expiring key-value store that's scoped to a user. When the user leaves a room, their data is deleted. That makes is great for things like "Who's in the room" presence indicators, live cursors, or shared highlighting effects.

To use presence, use a hook:

const presence = room.presence("joined");

document.onload = () => {
  presence.set(true);
};

presence.getAll(); // { "my-user-id": true }

Presence returns an object where the key is the user id you provide in your Auth Webhook, and the value is whatever you set for that presence key.

Made with 🌁 in San Francisco

hello@roomservice.dev

About Us

Copyright @ 2021

Room Service