Overview
Getting started with Weave.js React Helper library API
The WebSocket Store is a store for Weave.js that uses the WebSockets API as the transport layer to enable real-time collaboration over the internet. Built on top of Yjs’s y-websocket provider, it connects all clients to a central server that manages updates and syncs shared state almost instantly.
This store includes:
- A class named WeaveWebsocketsServer that helps setup the backend on top of an Express service.
- A class named WeaveStoreWebsockets 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 dependencies
First lets install the necessary dependencies on the backend-side:
npm install canvas ws y-protocols yjs @inditextech/weave-store-websockets
Server setup
Then setup your server as needed, here you have an example of a simple Express.js server, where we setup the WeaveWebsocketsServer class and the persistence is handled on the file system:
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs/promises";
import express from "express";
import { WeaveWebsocketsServer } from "@inditextech/weave-store-websockets/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 VALID_ROOM_WEBSOCKET_URL = /\/sync\/rooms\/(.*)/;
const host = process.env.HOST || "localhost";
const port = parseInt(process.env.PORT || "1234");
const app = express();
// prettier-ignore
const server = app.listen(port, host, (err: Error | undefined) => { // (2)
if (err) throw err;
// eslint-disable-next-line no-console
console.log(`Server started: http://${host}:${port}\n`);
});
// prettier-ignore
const wss = new WeaveWebsocketsServer({ // (3)
performUpgrade: async (request) => {
return VALID_ROOM_WEBSOCKET_URL.test(request.url ?? "");
}, // (4)
extractRoomId: (request) => {
const match = request.url?.match(VALID_ROOM_WEBSOCKET_URL);
if (match) {
return match[1];
}
return undefined;
}, // (5)
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;
}
}, // (6)
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);
}
}, // (7)
});
wss.handleUpgrade(server); // (8)
File explanation:
(1)
: Import the Weave.js server dependencies.(2)
: Extract the server when starting it.(3)
: Instantiate theWeaveWebsocketsServer
class. Which returns the WebSockets server.(4)
: Define theperformUpgrade
function that returns a boolean if the provided URL allows to trigger the HTTP/1.1 protocol upgrade mechanism (upgrades the connection from HTTP to Websocket), you receive the request as parameter.(5)
: Define theextractRoomId
function that return the room Id, you receive the request as parameter.(6)
: Define thefetchRoom
function that handles theget
part of the persistence, in this case we fetch a binary file with the shared-state from the file system if exists.(7)
: Define thepersistRoom
function that handles theset
part of the persistence, in this case we save to a file on the file system the shared-state.(8)
: Setup the server that can trigger the HTTP/1.1 protocol upgrade mechanism, in our case is the Express server we extracted and started before.
When an user tries to join a room, it calls the defined connection URL with the room Id where he
want to join, then the WeaveWebsocketsServer
validate that that URL with the performUpgrade
function,
if valid, it performs the HTTP/1.1 protocol upgrade, then a WebSocket connection is performed. Then the
room Id is extracted using the extractRoomId
function, and the server uses this information for the rest:
persistence, handling the messages sent over the websockets connections, etc.
Frontend-side usage
Setup the frontend is easy, you just need to follow this steps:
Install dependencies
First lets install the necessary dependencies for the frontend client:
npm install @inditextech/weave-store-websockets
Setup the client on the Weave instance
Then you need to add the WebSockets store client to the Weave instance in order to handle the connection when the user tries to join a room. This is done:
import { WeaveStoreWebsockets } from "@inditextech/weave-store-websockets/client"; // (1)
const roomId = "MyRoom"; // (2)
const store = new WeaveStoreWebsockets( // (3)
{
getUser, // (4)
undoManagerOptions: {
captureTimeout: 500,
}, // (5)
},
{
roomId, // (6)
wsOptions: {
serverUrl: "http://localhost:1234" // (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 theWeaveStoreWebsockets
class. Which returns the WebSockets store client.(4)
: Define thegetUser
function that return the metadata of the user that wants to connect.(5)
: We customize thecaptureTimeout
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 server URL where we want to connect on theserverUrl
parameter.(8)
: Set the instantiated store to the Weave instance.