Clear Connection, Wassily Kandinsky, 1925<br>PlaySocket<br>PlaySocket is a WebSocket-based synchronization library built for creating collaborative experiences, such as<br>multiplayer games.<br>The two unique aspects of PlaySocket are its CRDT-based architecture that allows for optimistic updates without any extra logic, and its synchronized storage that works beautifully with reactive frontend frameworks<br>such as React or Svelte.<br>Moreover, the library enables rapid prototyping, as complete<br>multiplayer logic can be client-only during active development with server-side validation added later. Validating<br>client input is made easy through callbacks and helper functions.<br>Security is a top priority of PlaySocket. Out of the box, it protects against XSS attacks and comes with WebSocket<br>message rate limiting.<br>The library is also lightweight, relying only on two dependencies:<br>MessagePack and WS.<br>The problem<br>When building a UX that combines user input with server requests, latency becomes troublesome. It feels odd when<br>a UI update isn't immediate.<br>Activate Instant
Activate Delayed
Optimistic updates – updating the UI before the request resolves, and reverting on error – are what developers reach<br>for in these scenarios.<br>When there's only one user making changes to an interface at a time, this is relatively trivial to do. The real<br>complexity arises when multiple users can interact with an interface simultaneously.<br>Ordering complexity<br>Let's think through a scenario where two users collaborate to pick a color in an optimistically-updated user<br>interface:<br>Michael selects green, and his UI shows green.<br>Lana selects blue, and her UI shows blue.<br>Server receives green and broadcasts it to all clients.<br>Lana's UI now shows green, Michael's already does.<br>Server receives blue and broadcasts it to all clients.<br>Lana's UI now shows blue again, Michael's now shows blue.<br>The issue with this flow is that Lana's UI briefly flashes green, even though that color was selected by Michael before her selection.<br>PlaySocket uses a vector clock to avoid this issue – with it, Lana's client,<br>upon receiving Michael's color update, knows that her selection is newer than Michael's and her UI remains unchanged.<br>Merging complexity<br>Ordering isn't the only thing that can go wrong when multiple users act at once. Let's think through a scenario<br>where two players add items to a shared, optimistically-updated inventory at the same time:<br>The inventory starts out as torch.<br>Michael adds rope, and his UI shows torch, rope.<br>Lana adds map, and her UI shows torch, map.<br>Server receives Michael's inventory torch, rope and broadcasts it.<br>Server receives Lana's inventory torch, map and broadcasts it.<br>Both UIs now show torch, map.<br>The issue with this flow is that each client sends the entire new value of the invetory, so whichever update arrives<br>last wins. Michael's addition to the inventory, the rope, is silently lost.<br>PlaySocket avoids this by describing changes as operations. Instead of<br>setting the whole inventory, both clients send an array-add operation. The two changes simply combine, and every client converges<br>to the expected result.<br>A quick example<br>The code below isn't quite production ready, but should give you an idea of how the library can be used to solve the<br>inventory problem.<br>pre]:text-sm [&>pre]:p-4 [&>pre]:overflow-x-auto relative group ">Client
import PlaySocket from 'playsocketjs';
const socket = new PlaySocket("michael's-id", {<br>endpoint: "wss://example.com/socket"<br>});
socket.onEvent("storageUpdated", (storage) => {<br>console.log("Current inventory:", storage.inventory);
// Assuming Lana has created a room<br>await socket.init();<br>await socket.joinRoom("lana's-room-id");
socket.updateStorage("inventory", "array-add", "rope");<br>pre]:text-sm [&>pre]:p-4 [&>pre]:overflow-x-auto relative group mt-4">Server
import PlaySocketServer from 'playsocketjs/server';
const server = new PlaySocketServer({ path: "/socket" });<br>The magic part here is that updateStorage() is synchronous. The<br>storage updates – and storageUpdated fires – immediately. You don't need<br>to think about handling optimistic updates yourself.<br>Get started<br>To get started with PlaySocket, please refer to the documentation.<br>Documentation GitHub repository