| src | ||
| .gitignore | ||
| biome.json | ||
| bun.lock | ||
| bunfig.toml | ||
| LICENSE | ||
| Makefile | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
@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
WebSocketis 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