{"id":13447910,"url":"https://github.com/bminer/ws-wrapper","last_synced_at":"2025-12-27T01:28:26.155Z","repository":{"id":44770802,"uuid":"75735278","full_name":"bminer/ws-wrapper","owner":"bminer","description":"Lightweight WebSocket lib with socket.io-like event handling, requests, and channels","archived":false,"fork":false,"pushed_at":"2025-01-27T21:30:20.000Z","size":222,"stargazers_count":78,"open_issues_count":1,"forks_count":13,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-17T13:55:32.195Z","etag":null,"topics":["browser","event-handlers","namespace","nodejs","promise","websocket","wrapper"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bminer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-06T13:45:23.000Z","updated_at":"2025-03-03T00:02:23.000Z","dependencies_parsed_at":"2024-01-06T22:29:49.138Z","dependency_job_id":"3ed899ea-29ab-4ed4-a5da-59870d186340","html_url":"https://github.com/bminer/ws-wrapper","commit_stats":{"total_commits":26,"total_committers":1,"mean_commits":26.0,"dds":0.0,"last_synced_commit":"6cc18b22663149659886dce75a9c9bf0953a45ba"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bminer%2Fws-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bminer%2Fws-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bminer%2Fws-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bminer%2Fws-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bminer","download_url":"https://codeload.github.com/bminer/ws-wrapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244893466,"owners_count":20527598,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","event-handlers","namespace","nodejs","promise","websocket","wrapper"],"created_at":"2024-07-31T05:01:30.070Z","updated_at":"2025-12-27T01:28:26.100Z","avatar_url":"https://github.com/bminer.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Tools per Language"],"sub_categories":["Node.js"],"readme":"# ws-wrapper\n\nLightweight and isomorphic [Web Socket](https://en.wikipedia.org/wiki/WebSocket)\nlib with socket.io-like event handling, Promise-based requests, and channels.\n\n## What?\n\nMuch like Socket.io, this library provides a protocol and API that sits on top\nof native WebSockets. Rather than passing raw messages through the WebSocket via\n[`WebSocket.send()`](\u003chttps://developer.mozilla.org/en-US/docs/Web/API/WebSocket#send()\u003e),\nthis library provides an RPC-like API that allows you to pass JSON data over\nWebSockets and trigger event handlers on the remote end. There is also a\nPromise-based request/response API, as well.\n\nThis library is isomorphic, so it can wrap WebSockets on the client (i.e.\nbrowser) or on a Node.js server using the [ws](https://github.com/websockets/ws)\nlibrary. You can get even fancier on the server side and utilize the\n[ws-server-wrapper](https://github.com/bminer/ws-server-wrapper) library\n(**recommended**).\n\n## Why?\n\nBecause lightweight is sometimes what you want. This library and its\ndependencies weigh under 3 KB when minified and gzipped!\n\nThis lib might be useful if you want some [socket.io](http://socket.io/docs/)\nfunctionality (i.e. namespaces, event handling, etc.), but you don't want all of\nthe [engine.io](https://github.com/socketio/engine.io) transports. When using\nthis library in conjunction with a library like\n[ws](https://github.com/websockets/ws), your real-time web application can be\npretty darn lightweight without giving up some nice bare-bones functionality.\n\n## Install\n\n```\nnpm install ws-wrapper\n```\n\n## Usage\n\nWebSocketWrapper is a CommonJS module, so it works in Node.js and in the browser\nif you use a bundler like Browserify, Webpack, Parcel.js, or\n[module-concat](https://github.com/bminer/module-concat).\n\nCheck out the\n[example-app](https://github.com/bminer/ws-wrapper/tree/master/example-app) for\na sample chat application (**recommended**).\n\nNote: This module uses ES6 classes. If you need this to work in IE or another\nold, decrepit browser, try using a code transpiler like\n[Babel](https://babeljs.io/).\n\nNote: This module uses `JSON.stringify` to serialize data over the raw WebSocket\nconnection. This means that serializing circular references is not supported out\nof the box.\n\n#### Client-side\n\n```javascript\n// Use a bundler to make the next line of code \"work\" on the browser\nimport WebSocketWrapper from \"ws-wrapper\"\n// Create a new socket\nvar socket = new WebSocketWrapper(new WebSocket(\"ws://\" + location.hostname))\n// Now use the WebSocketWrapper API... `socket.emit` for example\n// See examples below...\n```\n\n#### Server-side (Node.js)\n\nUse [ws-server-wrapper](https://github.com/bminer/ws-server-wrapper) to wrap the\nWebSocketServer (**recommended**). See ws-server-wrapper README for more\ndetails.\n\nIf you don't want to use ws-server-wrapper, you can wrap the WebSocket once a\nnew WebSocket connects like this:\n\n```javascript\nimport { WebSocketServer } from \"ws\"\nimport WebSocketWrapper from \"ws-wrapper\"\nvar wss = new WebSocketServer({ port: 3000 })\nwss.on(\"connection\", (socket) =\u003e {\n  socket = new WebSocketWrapper(socket)\n  // ...\n})\n```\n\n#### Server-side (Go)\n\nUse [ws-server-wrapper-go](https://github.com/bminer/ws-server-wrapper-go) to\nwrap your favorite WebSocket library. See\n[here for a complete example](https://pkg.go.dev/github.com/bminer/ws-server-wrapper-go@v0.0.0-20250119025659-ed3a0d67b7c5/adapters/coder#example-package)\nusing the [coder/websocket](https://github.com/coder/websocket) library.\n\n#### Other servers\n\nNo such libraries exist yet. :( Please create one, and let me know about it!\nI'll give you beer!\n\n## Event Handling\n\nIt's what you'd expect of an event handler API.\n\nCall `on` or `once` to bind an event handler to the `wrapper` or to a channel.\nCall `emit` to send an event.\n\nServer-side Example (_without using ws-server-wrapper_):\n\n```javascript\nimport { WebSocketServer } from \"ws\"\nimport WebSocketWrapper from \"ws-wrapper\"\nvar wss = new WebSocketServer({ port: 3000 })\nvar sockets = new Set()\nwss.on(\"connection\", (socket) =\u003e {\n  var socket = new WebSocketWrapper(socket)\n  sockets.add(socket)\n  socket.on(\"msg\", function (from, msg) {\n    // `this` refers to the WebSocketWrapper instance\n    console.log(`Received message from ${from}: ${msg}`)\n    // Relay message to all clients\n    sockets.forEach((socket) =\u003e {\n      socket.emit(\"msg\", from, msg)\n    })\n  })\n  socket.on(\"disconnect\", () =\u003e {\n    sockets.delete(socket)\n  })\n})\n```\n\nClient-side Example:\n\n```javascript\n// Use a bundler to make the next line of code \"work\" on the browser\nimport WebSocketWrapper from \"ws-wrapper\"\n// Establish connection\nvar socket = new WebSocketWrapper(new WebSocket(\"ws://\" + location.host))\n// Add \"msg\" event handler\nsocket.on(\"msg\", function (from, msg) {\n  console.log(`Received message from ${from}: ${msg}`)\n})\n// Emit \"msg\" event\nsocket.emit(\"msg\", \"my_name\", \"This is a test message\")\n```\n\n## Channels\n\nJust like in socket.io, you can \"namespace\" your events using channels. When\nsending messages to multiple channels, the same WebSocket connection is reused,\nbut the events are logically separated into their appropriate channels.\n\nBy default, calling `emit` directly on a WebSocketWrapper instance will send the\nmessage over the \"default\" channel. To send a message over a channel named\n\"foo\", just call `socket.of(\"foo\").emit(\"eventName\", \"yourData\")`.\n\n## Request / Response\n\nEvent handlers can return values or\n[Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\nto respond to requests. The response is sent back to the remote end.\n\nThe example below shows the client requesting data from the server, but\nws-wrapper also allows servers to request data from the client.\n\nServer-side Example (_without using ws-server-wrapper_):\n\n```javascript\nimport fs from \"node:fs\"\nimport { WebSocketServer } from \"ws\"\nimport WebSocketWrapper from \"ws-wrapper\"\nvar wss = new WebSocketServer({ port: 3000 })\nvar sockets = new Set()\nwss.on(\"connection\", (socket) =\u003e {\n  socket = new WebSocketWrapper(socket)\n  sockets.add(socket)\n  socket.on(\"userCount\", () =\u003e {\n    // Return value is sent back to the client\n    return sockets.size\n  })\n  socket.on(\"readFile\", (path) =\u003e {\n    // We can return a Promise that eventually resolves\n    return new Promise((resolve, reject) =\u003e {\n      // `path` should obviously be sanitized, but just go with it...\n      fs.readFile(path, (err, data) =\u003e {\n        // `err` or `data` are now sent back to the client\n        if (err) reject(err)\n        else resolve(data.toString(\"utf8\"))\n      })\n    })\n  })\n  socket.on(\"disconnect\", () =\u003e {\n    sockets.delete(socket)\n  })\n})\n```\n\nClient-side Example:\n\n```javascript\n// Assuming WebSocketWrapper is somehow available to this scope...\nvar socket = new WebSocketWrapper(new WebSocket(\"ws://\" + location.host))\nvar p = socket.request(\"userCount\")\n// `p` is a Promise that will resolve when the server responds...\np.then((count) =\u003e {\n  console.log(\"User count: \" + count)\n}).catch((err) =\u003e {\n  console.error(\"An error occurred while getting the user count:\", err)\n})\nsocket\n  .request(\"readFile\", \"/etc/issue\")\n  .then((data) =\u003e {\n    console.log(\"File contents:\", data)\n  })\n  .catch((err) =\u003e {\n    console.error(\"Error reading file:\", err)\n  })\n```\n\n## API\n\nClass: WebSocketWrapper\n\nA WebSocketWrapper simply wraps around a WebSocket to give you well-deserved\nfunctionality. :)\n\n`socket = new WebSocketWrapper(webSocketInstance[, options]);`\n\nConstructs a new WebSocketWrapper, and binds it to the native WebSocket\ninstance.\n\n- `webSocketInstance` - the native WebSocket instance\n- `options`\n  - `debug` - set to `true` to print debugging messages to `console.log`\n  - `errorToJSON` - function to serialize Errors over the WebSocket. In Node.js,\n    the default is to send only the `message` property of the Error (for\n    security reasons). Errors that occur on the browser include all properties.\n  - `requestTimeout` - maximum delay in ms. that the WebSocketWrapper will wait\n    until rejecting the Promise of a pending request. Defaults to `null`, which\n    means that there will be no timeout. This option is recommended for servers\n    because clients who do not fulfill pending requests can cause memory leaks.\n\nEvents\n\n- Event: \"open\" / \"connect\"\n  - `event` - The (worthless) event from the native WebSocket instance\n- Event: \"error\"\n  - `event` - The Error event from the native WebSocket instance\n- Event: \"message\"\n  - `event` - The\n    [Message event](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)\n    from the native WebSocket instance\n  - `data` - The message data (same as `event.data`)\n- Event: \"close\" / \"disconnect\"\n  - `event` - The\n    [Close event](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)\n    from the native WebSocket instance\n  - `wasOpen` - `true` if the \"open\" event was fired on the native WebSocket\n    instance before the \"close\" event was fired.\n\n_Note: The \"special\" events listed above are not sent over the WebSocket._\n\nThe EventEmitter-like API looks like this:\n\n- `socket.on(eventName, listener)` Adds the `listener` function to the end of\n  the listeners array for the event named `eventName`. When an event or request\n  matching the `eventName` is received by the WebSocket, the `listener` is\n  called.\n\n  Values returned by the `listener` callback are used to respond to requests\n  (see `socket.request`). If the return value of the `listener` is a `Promise`,\n  the response to the request will be sent once the Promise is resolved or\n  rejected; otherwise, the return value of the `listener` is sent back to the\n  remote end immediately.\n\n  If the inbound message is a simple event (see `socket.emit`), the return value\n  of the `listener` is ignored. It is also \"safe\" for the `listener` to return a\n  `Promise` even if the inbound message is a \"simple\" event. If the returned\n  `Promise` is rejected, an unhandled rejection will not occur; rather, the\n  result of the Promise is just ignored.\n\n  If the `listener` throws an Error, this Error will propagate up the stack as\n  expected, and if the inbound message was a request, the Error is sent back to\n  the remote end as a response rejection.\n\n- `socket.once(eventName, listener)` Adds a one time `listener` function for the\n  event named `eventName`.\n- `socket.removeListener(eventName, listener)` Removes the specified `listener`\n  from the listener array for the event named `eventName`.\n- `socket.removeAllListeners([eventName])` Removes all listeners, or those of\n  the specified `eventName`.\n- `socket.eventNames()` Returns an array listing the events for which the\n  emitter has registered listeners.\n- `socket.listeners(eventName)` Returns a copy of the array of listeners for the\n  event named `eventName`.\n- `socket.emit(eventName[, ...args])` Sends an event down the WebSocket with the\n  specified `eventName` calling all listeners for `eventName` on the remote end,\n  in the order they were registered, passing the supplied arguments to each.\n- `socket.request(eventName[, ...args])` Sends a request down the WebSocket with\n  the specified `eventName` and returns a\n  [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\n  that will resolve once the remote event listener responds.\n\n  **Note**: While it is common design for only one event listener to exist on\n  the remote end, all listeners for `eventName` on the remote end are called, in\n  the order they were registered, passing the supplied arguments to each. Since\n  Promises can only be resolved or rejected once, only the data from the first\n  event listener is used to generate the response for this request.\n\n  **Note**: If a request is sent, but there is no remote event listener to\n  respond to the request, a response rejection is immediately sent back by the\n  remote end.\n\n- `socket.use(function fn(eventName, args, next) {...})` Adds a middleware\n  function `fn` to receive all messages for the channel. The `eventName`\n  indicates the name of the event or request, and the `args` are the arguments\n  to be passed to the respective event handler. `next([err])` should be called\n  to continue processing to the next middleware function. Once all middleware\n  have processed the event and called `next`, the event is then processed by the\n  event handler for the `eventName`.\n- `socket.timeout(tempTimeoutInMs)` Temporarily sets the `requestTimeout` to\n  `tempTimeoutInMs` for the next request only. This returns `socket` to allow\n  chaining. Typical usage:\n  ```javascript\n  // The next request will be rejected if there is no response for 5 secs.\n  let promise = socket.timeout(5 * 1000).request(\"readFile\", \"/etc/issue\")\n  ```\n\nThe above EventEmitter functions like `on` and `once` are chainable (as\nappropriate).\n\nChannel API:\n\n- `socket.of(channelName)` Returns the channel with the specified `channelName`.\n  Every channel has the same EventEmitter-like API as described above for\n  sending and handling channel-specific events and requests. A channel also has\n  a read-only `name` property.\n\nOther methods and properties:\n\nBy default, the WebSocketWrapper provides a queue for data to be sent. Once the\nWebSocket is open, this queue is flushed until the connection is lost. The\nfollowing methods allow one to re-bind a new WebSocket or clear the send queue.\n\n- `socket.abort()` Clears the send queue for this WebSocketWrapper and rejects\n  all Promises for pending requests.\n- `socket.bind(nativeWebSocket)` Binds this WebSocketWrapper to a new WebSocket.\n  This can be useful when socket reconnection logic needs to be implemented.\n  Instead of creating a new WebSocketWrapper each time a WebSocket is\n  disconnected, one can simply bind a new WebSocket to the WebSocketWrapper. In\n  this way, data queued to be sent while the connection was dead will be sent\n  over the new WebSocket passed to the `bind` function.\n- `socket.isConnecting` - checks the native WebSocket `readyState` and is `true`\n  if and only if the state is CONNECTING.\n- `socket.isConnected` - checks the native WebSocket `readyState` is `true` if\n  and only if the state is CONNECTED.\n- `socket.send(data)` If connected, calls the native WebSocket's `send` method;\n  otherwise, the data is added to the WebSocketWrapper's send queue.\n- `socket.disconnect()` Closes the native WebSocket\n- `socket.set(key, value)` Saves user data specific to this WebSocketWrapper\n- `socket.get(key)` Retrieves user data. See `socket.set(key, value)` above.\n\n`WebSocketWrapper.MAX_SEND_QUEUE_SIZE` The maximum number of items allowed in\nthe send queue. If a user tries to send more messages than this number while a\nWebSocket is not connected, errors will be thrown. Defaults to 10; changes\naffect all WebSocketWrapper instances.\n\n## Protocol\n\nAll data passed over the native WebSocket should be valid JSON, but this is not\na hard requirement. [ws-wrapper](https://github.com/bminer/ws-wrapper/) will try\nto parse a JSON string and determine the message type based on the properties in\nthe parsed Object.\n\nThe following message types are defined by ws-wrapper:\n\n1. **Event Dispatch** - Identified by an Object with `a` key but no `i` key. The\n   channel name is optional.\n\n   ```javascript\n   {\n    \"c\": \"channel_name\",\n    \"a\": [\"event_name\", \"first_arg\", \"second_arg\", \"last_arg\"]\n   }\n   ```\n\n   The client or server can send events. Events are nothing more than an event\n   name and some data, passed as arguments to the event handler.\n\n1. **Request** - Identified by an Object with `a` and `i` keys where `i` refers\n   to the unique request identifier. The channel name is optional.\n\n   ```javascript\n   {\n    \"i\": 123,\n    \"c\": \"channel_name\",\n    \"a\": [\"event_name\", \"first_arg\", \"second_arg\", \"last_arg\"]\n   }\n   ```\n\n   The client or server can send a Request, which is essentially an Event that\n   needs some sort of server Response.\n\n1. **Response (Resolution)** - Identified by an Object with `i` and `d` keys\n   where `i` is the request identifier and `d` is the response data.\n\n   ```javascript\n   {\n    \"i\": 123,\n    \"d\": {\"resolved\": \"data\", \"hello\": \"world\"}\n   }\n   ```\n\n1. **Response (Rejection)** - Identified by an Object with `i` and `e` keys\n   where `i` is the request identifier and `e` is the error Object to be used\n   when rejecting the response Promise. If `_` is set, the `e` Object is\n   converted into an Error instance upon receipt.\n\n   ```javascript\n   {\n    \"i\": 123,\n    \"e\": {\"message\": \"error message\"},\n    \"_\": 1\n   }\n   ```\n\nIf the message received by the WebSocket is not valid JSON or if the parsed\nObject does not match one of the above message types, then the message is simply\nignored by ws-wrapper. Also if the JSON message contains a `ws-wrapper` property\nwith the value `false`, the message will be ignored. This allows other libraries\nto use the same WebSocket and send messages that will not be processed by\nws-wrapper.\n\n## Auto-Reconnect\n\nws-wrapper does not implement auto-reconnect functionality out of the box. For\nthose who want it (_almost_ everyone), I have written some sample code to show\nhow easy it is to add.\n\n[How to implement auto-reconnect for ws-wrapper](https://github.com/bminer/ws-wrapper/wiki/Client-side-Auto-Reconnect)\n\nIf someone wants to make an npm package for the auto-reconnect feature, I'd be\nhappy to list it here, but it will probably never be a core ws-wrapper feature.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbminer%2Fws-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbminer%2Fws-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbminer%2Fws-wrapper/lists"}