From ee05704493fb540eae436f9074af3f98d282a60b Mon Sep 17 00:00:00 2001 From: Endeavorance Date: Tue, 7 Jan 2025 15:44:49 -0500 Subject: [PATCH] Add readme --- LICENSE | 3 + README.md | 147 +++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- src/managed-socket.ts | 12 ++-- 4 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d11c8b8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +Licensed under CC BY-NC-SA 4.0. + +To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ \ No newline at end of file diff --git a/README.md b/README.md index 83301f0..fe259b1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,146 @@ -# socket-speak +# @endeavorance/socket -To install dependencies: +Lightweight reconnecting websocket interface -```bash -bun install +## ManagedSocket + +A self-reconnecting wrapped WebSocket with built-in heartbeats + +### `new ManagedSocket(url: string): ManagedSocket` + +Create a new `ManagedSocket` instance and connect to the provided URL via WebSocket. + +The provided URL should use the `ws://` or `wss://` protocol. + +```typescript +import { ManagedSocket } from "@endeavorance/socket"; + +const mySocket = new ManagedSocket("wss://socket.my-server.web"); ``` -To run: -```bash -bun run src/index.ts +### `.send(data: T)` + +Sends the provided data over the socket. + +The data can be of any shaoe 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. + +```typescript +const mySocket = new ManagedSocket("..."); +mySocket.send("hello!"); +mySocket.send({ + command: "do-something", + args: ["hello", "world"], +}); ``` -This project was created using `bun init` in bun v1.1.42. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. +### `.onMessage(handler: SocketDataHandler)` + +Provide a function to invoke whenever the socket receives a message meant to be handled by your application. + +This handler will be passed the blob of data contained within the message, which is typed as `unknown`. + +Your handler function should parse the incoming data to ensure it is of the shape you expect. + +```typescript +const handler = (data: unknown) => { + console.log("Got some data: " + data); +} + +const mySocket = new ManagedSocket("..."); +mySocket.onMessage(handler); +``` + +### `.onError(handler: SocketErrorHandler)` + +Provide a function to invoke when the socket encounters a non-catastrophic error. + +Not catastrophic errors may occur during the lifecycle of the WebSocket connection, but are different from Catastrophic errors which only occur if the connection was terminated with a non-reconnect code. + +This function will be passed a `ManagedSocketError`. If no error handler is set, errors will instead be thrown. + +```typescript +const errorHandler = (error: ManagedSocketError) => { + console.error(error); +} + +const mySocket = new ManagedSocket("..."); +mySocket.onError(handler); +``` + +### `.onCatastrophicError(handler: SocketCatastrophicErrorHandler)` + +Provide a function to invoke when the socket is terminated with a non-reconnect code. When this handler is invoked, it means the socket connection is terminated and unable to be recovered automatically. Your handler will be passed the `SocketDisconnectCode` associated with the connection termination. + +You may still attempt to manuall reconnect using the `.reconnect()` method, but it will not be automatic. + +```typescript +const bigOops = (code) => { + console.error(`Welp, we're done here. Closed with code: ${code}`); +} + +const mySocket = new ManagedSocket("..."); +mySocket.onCatastrophicError(bigOops); +``` + +### `.onOpen(SocketOpenHandler)` + +Provide a function to be invoked when the connection is opened. + +No arguments are provided to this handler. + +### `.onClose(SocketCloseHandler)` + +Provide a function to be invoked when the connection closes cleanly. + +No arguments are provided to this handler. + +### `.reconnect()` + +Attempts to reconnect the socket using an exponential backoff algorithm. Each failed connection attempt will wait a bit longer before the next try. + +### `.close()` + +Safely close the socket connection. + +## `SocketSpeak` + +`SocketSpeak` is a set of utilities for working with the `ManagedSocket` data interface. It is lower-level and meant primarily for writing servers. + +All methods are defined on the `SocketSpeak` object, exported by the library. + +```typescript +import { SocketSpeak } from "@endeavorance/socket" +``` + +### `createDataMessage(data: T): SocketDataMessage` + +Wraps the provided data in a well formatted message ready to serialize and send over the socket. + +A `SocketDataMessage` is used for sending application data over the socket. + +### `createHeartbeatMessage(): SocketHeartbeatMessage` + +Creates a well formatted heartbeat message to serialize and send over the socket. + +A `SocketHeartbeatMessage` is used internally to keep the connection alive and carries a data payload of the current timestamp as a string. + +### `serialize(message: SocketMessage): SerializedSocketMessage` + +Serialize any `SocketMessage`. Returns a `SerializedSocketMessage` which is a branded `string` type for type safety. + +### `deserialize(data: string): SocketMessage` + +Attempts to deserialize the provided string into a `SocketMessage`. Throws a `ManagedSocketError` if the deserialization fails or the deserialized shape is invalid. + +Note: This is not to deserialize your application data messages. The managed socket handles wrapping and unwrapping messages before handing the payload to your application. + +### `prepare(data: T)` + +Shorthand for `serialize(createDataMessage(data)) + +### `isCatastrophicCloseCode(code: SocketCloseCode)` + +Returns `true` if the provided `SocketCloseCode` is a non-reconnect close code, or `false` otherwise. \ No newline at end of file diff --git a/package.json b/package.json index 516e5dc..84ea233 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@endeavorance/socket-speak", + "name": "@endeavorance/socket", "description": "Lightweight reconnecting websocket interface", "version": "0.0.1", "exports": "./dist/index.js", diff --git a/src/managed-socket.ts b/src/managed-socket.ts index d6d518b..2e43c22 100644 --- a/src/managed-socket.ts +++ b/src/managed-socket.ts @@ -158,7 +158,7 @@ export class ManagedSocket { return this.ws; } - handleCatastrophicError(code: SocketDisconnectCode) { + private handleCatastrophicError(code: SocketDisconnectCode) { this.state = "terminated"; this.attemptReconnect = false; this.debugMessage( @@ -182,18 +182,22 @@ export class ManagedSocket { this.onMessageHandler = handler; } - onError(handler: (error: ManagedSocketError) => void) { + onError(handler: SocketErrorHandler) { this.onErrorHandler = handler; } - onOpen(handler: () => void) { + onOpen(handler: SocketOpenHandler) { this.onOpenHandler = handler; } - onClose(handler: () => void) { + onClose(handler: SocketCloseHandler) { this.onCloseHandler = handler; } + onCatastrophicError(handler: SocketCatastrophicErrorHandler) { + this.onCatastrophicErrorHandler = handler; + } + reconnect() { this.attemptReconnect = true; this.state = "reconnecting";