A reactive, optimistic WebSocket library that simplifies game & app development by abstracting away complex sync logic.
PlaySocket eliminates the traditional complexity of collaborative experiences:
- Streamlined architecture: No additional backend code is required, but server-authoritative behavior supported
- State synchronization: Built-in storage system keeps the full state synchronized across all clients, always conflict-free and in order
- Resilient & secure connections: Automatic reconnection handling & strict rate-limiting
- Lightweight: Uses WebSockets for efficient, predictable, reliable communication and has little dependencies
npm install playsocketjsNote that in production, you should always try...catch promises, such as socket.init() – they can reject!
Initializing the client:
import PlaySocket from 'playsocketjs';
// Create a new instance
const socket = new PlaySocket('unique-client-id', { // You can pass no ID to let the server pick one
endpoint: 'wss://example.com/socket'
});
// Set up event handlers (optional)
socket.onEvent('status', status => console.log('Status:', status));
socket.onEvent('error', status => console.log('Error:', status));
const clientId = await socket.init(); // Initialize the socketCreating a room:
// Create a new room
const roomId = await socket.createRoom();
// Optionally, with initial storage
const roomId = await socket.createRoom({
players: ["this-player"],
latestPlayer: null,
});Joining a room:
await socket.joinRoom('room-id'); // Join an existing roomLeaving a room:
socket.destroy(); // To leave the room, destroy the instanceUsing the storage update event for reactivity:
const reactiveVariable = useState(); // Or $state(), reactive(), depending on your framework
socket.onEvent('storageUpdated', storage => (reactiveVariable = storage)); // Assign on updateInterfacing with the synchronized storage (examples):
const currentState = socket.getStorage; // Synchronous, local access
socket.updateStorage('players', 'array-add-unique', { username: 'Player4', level: 2 }); // Special method to enable conflict-free additions for arrays
socket.updateStorage('latestPlayer', 'set', 'Player4'); // Regular synced storage updateSending traditional requests to the server:
socket.sendRequest('chosen-request-name', { fact: "You can build server-authoritative logic using this!" })Creates a new PlaySocket instance with a specified ID and configuration options.
The ID can be set to null to let the server pick a unique one.
new PlaySocket(id?: string, options: PlaySocketOptions)| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string | none - set this! |
WebSocket server endpoint (e.g., 'wss://example.com/socket') |
customData |
object | {} |
Arbitrary data to pass to the "clientRegistered" server event (optional) |
debug |
boolean | false |
Set to true to enable extra logging |
| Method | Parameters | Return type | Description |
|---|---|---|---|
init() |
- | Promise<string> |
Initialize the WebSocket connection – Returns Promise which resolves with the client's ID |
createRoom() |
initialStorage?: object, size?: number |
Promise<string> |
Create a new room and become host – Returns Promise which resolves with the room ID. The room participant maximum is 100 |
joinRoom() |
roomId: string |
Promise<void> |
Join an existing room |
destroy() |
- | void |
Use this to leave a room and close the connection |
updateStorage() |
key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any |
void |
Update a key in the shared storage (max. 100 keys). Array operation types allow for conflict-free simultaneous array updates. For '-matching' operations, value becomes the value to match, and updateValue the replacement |
sendRequest() |
name: string, data?: any |
void |
Send requests to the server with optional custom data (handle these in the requestReceived server event) |
onEvent() |
event: string, callback: Function |
void |
Register an event callback |
| Event | Callback parameter | Description |
|---|---|---|
status |
status: string |
Connection status updates |
error |
error: string |
Error events |
instanceDestroyed |
- | Destruction event - triggered by manual .destroy() method invocation or by fatal errors and disconnects |
storageUpdated |
storage: object |
Storage state changes |
hostMigrated |
roomId: string |
Host changes |
clientConnected |
clientId: string |
New client connected to the room |
clientDisconnected |
clientId: string, roomId?: string |
Client disconnected from the room |
| Property | Type | Description |
|---|---|---|
id |
string | Client's unique identifier on the WebSocket server |
isHost |
boolean | If this user is currently assigned the host role |
connectionCount |
number | Number of active client connections in room (without yourself) |
getStorage |
object | Retrieve storage object |
PlaySocket includes a server implementation that can be set up in seconds.
To use the server component, you'll need to install playsocketjs and the ws package:
npm install playsocketjs wsHere are usage examples for a standalone server and an Express.js application.
import PlaySocketServer from 'playsocketjs/server'; // Both ES Module & CommonJS Module syntax is supported
const server = new PlaySocketServer(); // Create and start the server (default path is /)
// Gracefully disconnect all clients and close the server (optional)
function shutdown() {
server.stop();
process.exit(0);
}
// Handle both SIGINT (Ctrl+C) and SIGTERM (Docker stop)
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);const express = require('express');
const http = require('http');
const PlaySocketServer = require('playsocketjs/server');
const app = express();
const httpServer = http.createServer(app);
// Create PlaySocket server with your HTTP server
const playSocketServer = new PlaySocketServer({
server: httpServer,
path: '/socket'
});
// Start the server
httpServer.listen(3000, () => {
console.log('Server running on port 3000');
});
// Gracefully disconnect all clients and close the server (recommended)
function shutdown() {
server.stop();
process.exit(0);
}
// Handle both SIGINT (Ctrl+C) and SIGTERM (Docker stop)
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);Creates a new PlaySocket Server instance with configuration options.
new PlaySocket(options: PlaySocketServerOptions)| Option | Type | Default | Description |
|---|---|---|---|
port |
number | 3000 | Port to listen on (used only if no server provided) |
path |
string | '/' | WebSocket endpoint path |
server |
http.Server | - | Existing http server (optional) |
rateLimit |
number | 20 | Adjust the messages/second rate limit |
debug |
boolean | false | Set to true to enable extra logging |
| Method | Parameters | Return type | Description |
|---|---|---|---|
stop() |
- | void |
Closes all active client connections, the websocket server and the underlying http server if it's standalone |
kick() |
clientId: string, reason?: string |
void |
Kick a client by their clientID – this will close their connection and set an error message |
onEvent() |
event: string, callback: Function |
void |
Register a server-side event callback |
getRoomStorage() |
roomId: string |
object |
Get a snapshot of the current room storage |
updateRoomStorage() |
roomId: string, key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any |
void |
Update a key in the shared room storage from the server |
createRoom() |
initialStorage?: object, size?: number, host?: string |
object |
Create a room (returns object containing room ID and state) – Rooms created with a non-player host like "server" (default) will not be deleted when the last participant leaves |
destroyRoom() |
roomId: string |
void |
Destroy a room & kick all participants |
| Event | Callback parameters | Description | Return for action |
|---|---|---|---|
clientRegistered |
clientId: string, customData: object |
Client registered with the server | - |
clientRegistrationRequested |
clientId: string, customData: object |
Client requests to register | Return false or rejection reason string to block |
clientDisconnected |
clientId: string |
Client disconnected from the server | - |
clientJoinedRoom |
clientId: string, roomId: string |
Client joined a room (clients can only leave by disconnecting) | - |
clientJoinRequested |
clientId: string, roomId: string |
Client requests to join a room | Return false or rejection reason string to block |
roomCreated |
roomId: string |
Client created a room | - |
roomDestroyed |
roomId: string |
Room was destroyed (happens when all participants leave) | - |
roomCreationRequested |
{clientId: string, initialStorage: object} |
Room creation requested by client | Return object to override initial storage, false to deny |
storageUpdated |
{clientId: string, roomId: string, update: object, storage: object} |
Room storage property updated | - |
storageUpdateRequested |
{clientId: string, roomId: string, update: object, storage: object} |
Room storage property update requested by client | Return false to block the update |
requestReceived |
{clientId: string, roomId?: string, requestName: string, data?: any} |
Request from client was received by the server | - |
| Property | Type | Description |
|---|---|---|
getRooms |
object | Retrieve the rooms object |
MIT