A durable WebSocket wrapper
Find a file
2025-12-05 15:01:13 -05:00
src Update readme, format 2025-12-05 15:00:59 -05:00
.gitignore Initial commit 2025-11-15 10:03:08 -05:00
biome.json 2.0.0 2025-12-01 16:01:11 -05:00
bun.lock 3.0.0 2025-12-03 11:48:27 -05:00
bunfig.toml 2.0.0 2025-12-01 16:01:11 -05:00
LICENSE Add license 2025-12-01 17:39:57 -05:00
Makefile Initial commit 2025-11-15 10:03:08 -05:00
package.json 4.0.0 2025-12-05 15:01:13 -05:00
README.md Update readme, format 2025-12-05 15:00:59 -05:00
tsconfig.json Initial commit 2025-11-15 10:03:08 -05:00

@endeavorance/socket

A durable WebSocket wrapper

Functionality

This library exports ManagedSocket, a TypeScript/JavaScript class which wraps the native WebSocket API to provide improved stability and ergonomics.

  • Works anywhere that WebSocket is available (browser, bun, etc)
  • Automatic reconnections with exponential backoff
  • Network connectivity monitoring when used in browsers
  • Managed keep-alive heartbeats
  • Automatic serialization/deserialization of data
  • Offline message queue for disconnection tolerance
const socket = new ManagedSocket("ws://localhost");

socket.onMessage = (data) => {
  // Deserialized JSON of type `unknown`
  console.log(data);
};

socket.send({
  hello: "world!",
});

API

new ManagedSocket(url: string, handlers?: EventHandlers): ManagedSocket

Create a new ManagedSocket instance and connect to the provided URL via WebSocket.

The provided URL should use the ws:// or wss:// protocol.

import { ManagedSocket } from "@endeavorance/socket";

// Create a socket. All properties in the opts param are optional and are
// shown here with their default values.
const mySocket = new ManagedSocket("wss://socket.my-server.web", {
  // Runs whenever the underlying socket connects to the server
  onConnected: () => {},

  // Runs whenever the underlying socket drops a connection
  onDisconnected: () => {},

  // Runs whenever data comes in over the socket
  // `data` is passed in as a deserialized object of an unknown shape
  onMessage: (data: unknown) => {},

  // Runs whenever a heartbeat comes in over the socket
  // Includes the timestamp from in the heartbeat to allow monitoring ping
  onHeartbeat: (timestamp: number) => {},

  // Runs whenever the underlying socket emits an error event
  // If not provided, such errors will instead be thrown
  onError: (error: ManagedSocketError) => {},

  // Runs whenever the ManagedSocket state changes
  onStateChanged(from: SocketState, to: SocketState) => {},

  // How many milliseconds between heartbeats
  heartbeatInterval: 30000,

  // If this socket should auto-reply to heartbeat messages instantly.
  // Do not enable this and also instantly reply on the server side, or you
  // will end up pinging back and forth forever
  replyToHeartbeats: false,

  // How errors from the underlying socket should be handled
  // Defaults to "report" when `onError` is provided, otherwise "throw"
  // Specifying a behavior explicitly in the options overrides that
  socketErrorBehavior: "throw",
});

.send<T>(data: T)

Sends the provided data over the socket.

The data can be of any shape as long as it can be serialized (using JSON.stringify under the hood).

If send() is called on a ManagedSocket instance that is not currently connected, it will be enqueued to be sent once the connection is reestablished.

const mySocket = new ManagedSocket("...");
mySocket.send("hello!");
mySocket.send({
  command: "do-something",
  args: ["hello", "world"],
});

.pause()

Temporarily close the socket.

.connect()

Reconnect to the server after previously pausing.

.terminate()

Permanently close the socket.

Utilities

createDataMessage<T>(payload: T): DataMessage

Wrap arbitrary data to be serialized and sent over the socket

createHeartbeatMessage(timestamp?: number): HeartbeatMessage

Create a heartbeat message to be serialized and sent over the socket

serializeMessage<T>(message: SocketMessage): SerializedSocketMessage

Serialize a message to send over the socket

deserializeMessage(data: string): SocketMessage

Deserializes a serialized message back into a SocketMessage.

handleRawMessageData(rawMsg, onData, onHeartbeat)

Parses and routes incoming socket messages to a relevant handler function.

Pass the raw socket data in as rawMsg, and at least an onData handler. The onHeartbeat handler is optional and defaults to a noop.

onData() is passed the data sent in the message. onHeartbeat() is passed the timestamp of the heartbeat.

Throws a ManagedSocketInvariant when invalid data is processed