Weave.js

Overview

Getting started with Weave.js Azure Web PubSub store API

The Azure Web PubSub Store is a store for Weave.js that uses the Azure Web PubSub cloud infrastructure as the transport layer to enable real-time collaboration over the internet.

This store includes:

  • A class named WeaveAzureWebPubsubServer that helps setup the backend on top of an Express service.
  • A class named WeaveStoreAzureWebPubsub which is the client used on the frontend side to connect to the store backend and provide all the support for the real-time management of the shared-state.

Backend-side usage

Setup the frontend is easy, you just need to follow this steps on your Express-based backend:

Install the WebSocket Store backend dependencies

First lets install the necessary dependencies on the backend-side:

npm install canvas @azure/web-pubsub @azure/web-pubsub-express y-protocols yjs @inditextech/weave-store-azure-web-pubsub

Setup the server

Then setup your server as needed, here you have an example of a simple server, where the persistence is handled on the file system:

server.ts
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs/promises";
import cors from "cors";
import express, { Router } from "express";
import { WeaveAzureWebPubsubServer } from "@inditextech/weave-store-azure-web-pubsub/server"; // (1)

// eslint-disable-next-line @typescript-eslint/naming-convention
const __filename = fileURLToPath(import.meta.url);
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(__filename);

const host = process.env.WEAVE_AZURE_WEB_PUBSUB_HOST || "localhost";
const port = parseInt(process.env.WEAVE_AZURE_WEB_PUBSUB_PORT || "1234");

const endpoint = process.env.WEAVE_AZURE_WEB_PUBSUB_ENDPOINT; // (2)
const key = process.env.WEAVE_AZURE_WEB_PUBSUB_KEY; // (2)
const hubName = process.env.WEAVE_AZURE_WEB_PUBSUB_HUB_NAME; // (2)

if (!endpoint || !key || !hubName) {
  throw new Error("Missing required environment variables");
}

// prettier-ignore
const azureWebPubsubServer = new WeaveAzureWebPubsubServer({ // (3)
  pubsubConfig: {
    endpoint,
    key,
    hubName,
  }, // (4)
  fetchRoom: async (docName: string) => {
    try {
      const roomsFolder = path.join(__dirname, "rooms");
      const roomsFile = path.join(roomsFolder, docName);
      return await fs.readFile(roomsFile);
    } catch (e) {
      return null;
    }
  }, // (5)
  persistRoom: async (
    docName: string,
    actualState: Uint8Array<ArrayBufferLike>
  ) => {
    try {
      const roomsFolder = path.join(__dirname, "rooms");

      let folderExists = false;
      try {
        await fs.access(roomsFolder);
        folderExists = true;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
      } catch (e) {
        folderExists = false;
      }

      if (!folderExists) {
        await fs.mkdir(roomsFolder, { recursive: true });
      }

      const roomsFile = path.join(roomsFolder, docName);
      await fs.writeFile(roomsFile, actualState);
    } catch (ex) {
      console.error(ex);
    }
  }, // (6)
});

const app = express();

const corsOptions = {
  origin: true,
};

app.use(cors(corsOptions));

const router = Router();

router.use(azureWebPubsubServer.getMiddleware()); // (7)
router.get(`/rooms/:roomId/connect`, async (req, res) => {
  const roomId = req.params.roomId;
  const url = await azureWebPubsubServer.clientConnect(roomId);
  res.json({ url });
}); // (8)

app.use(`/api/v1/${hubName}`, router); // (9)

app.listen(port, host, (err: Error | undefined) => {
  // (10)
  if (err) throw err;

  // eslint-disable-next-line no-console
  console.log(`Server started @ http://${host}:${port}\n`);
  // eslint-disable-next-line no-console
  console.log(`Connection endpoint: /api/v1/${hubName}/{roomId}/connect`);
});

File explanation:

  • (1): Import the Weave.js server dependencies.
  • (2): Define the endpoint, key and hubName of the Azure Web PubSub instance where we will connect.
  • (3): Instantiate the WeaveAzureWebPubsubServer class. Which returns the server utilities to handle Azure Web PubSub connections.
  • (4): Define the Azure Web Pubsub properties.
  • (5): Define the fetchRoom function that handles the get part of the persistence, in this case we fetch a binary file with the shared-state from the file system if exists.
  • (6): Define the persistRoom function that handles the set part of the persistence, in this case we save to a file on the file system the shared-state.
  • (7): Setup the middleware that handles the Azure Web PubSub connections.
  • (8): Setup a route that is responsible to call the clientConnect method of the server in order to obtain a secure connection URL to the Azure Web PubSub Hub.
  • (9): Setup the server routes.
  • (10): Start the server.

When an user tries to join a room, it calls the defined route with the room Id where he want to join, then the WeaveAzureWebPubsubServer using the clientConnect function, call the Azure Web PubSub Hub to extract a secure connection and this URL is passed as response to the client. Then the client connects to this URL and the server does the rest: persistence, handling the messages sent over the connection, etc.

Frontend-side usage

Setup the frontend is easy, you just need to follow this steps:

Install the WebSocket Store frontend dependency

First lets install the necessary dependencies for the frontend client:

npm install @inditextech/weave-store-azure-web-pubsub

Setup the client on the Weave instance

Then you need to add the Azure Web PubSub store client to the Weave instance in order to handle the connection when the user tries to join a room. This is done:

import { WeaveStoreAzureWebPubsub } from "@inditextech/weave-store-azure-web-pubsub/client"; // (1)

const roomId = "MyRoom"; // (2)

const store = new WeaveStoreAzureWebPubsub( // (3)
  {
    getUser, // (4)
    undoManagerOptions: {
      captureTimeout: 500,
    }, // (5)
  },
  {
    roomId, // (6)
    url: http://localhost:1234/api/v1/rooms/${roomId}/connect`, // (7)
  }
);

const instance = new Weave(
  {
    ...
    store, // (8)
  },
  ...
);

Explanation:

  • (1): Import the Weave.js client dependencies.
  • (2): Define the room Id to connect to.
  • (3): Instantiate the WeaveStoreAzureWebPubsub class. Which returns the Azure Web PubSub store client.
  • (4): Define the getUser function that return the metadata of the user that wants to connect.
  • (5): We customize the captureTimeout of the Undo-Redo manager of the store, to capture changes every 500ms.
  • (6): We set the room Id where we want to connect to.
  • (7): Define url where we want to connect and fetch the connection URL to the Azure Web PubSub.
  • (8): Set the instantiated store to the Weave instance.