{"id":30180939,"url":"https://github.com/streamich/nmsg-rpc","last_synced_at":"2025-08-12T08:06:25.100Z","repository":{"id":50949604,"uuid":"56229943","full_name":"streamich/nmsg-rpc","owner":"streamich","description":"RPC event based router for JavaScript bi-directional messaging","archived":false,"fork":false,"pushed_at":"2025-07-14T05:47:01.000Z","size":113,"stargazers_count":3,"open_issues_count":14,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-09T12:05:48.215Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/streamich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-04-14T10:53:44.000Z","updated_at":"2025-02-11T15:48:36.000Z","dependencies_parsed_at":"2024-06-19T08:29:13.199Z","dependency_job_id":"46e81e44-f756-47b6-8abc-326934ffb668","html_url":"https://github.com/streamich/nmsg-rpc","commit_stats":{"total_commits":36,"total_committers":4,"mean_commits":9.0,"dds":"0.13888888888888884","last_synced_commit":"8d30b8a5f382cac0f1e0ce909a4b00068eca9e93"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/streamich/nmsg-rpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/streamich%2Fnmsg-rpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/streamich%2Fnmsg-rpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/streamich%2Fnmsg-rpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/streamich%2Fnmsg-rpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/streamich","download_url":"https://codeload.github.com/streamich/nmsg-rpc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/streamich%2Fnmsg-rpc/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269757192,"owners_count":24470727,"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","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-08-12T08:06:11.612Z","updated_at":"2025-08-12T08:06:25.087Z","avatar_url":"https://github.com/streamich.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RPC (Remote Procedure Calls) for Node.js Messengers\r\n\r\n\u003e **What is `nmsg-rpc`?**\r\n\r\n\u003e Let's say you have two connected sockets and all they can do is send and receive\r\nmessages from each other out of order, but you want to do request/response\r\ntype of communication or use `.on('event', cb)` and `.emit('event', data, cb)`\r\nevent-based routing -- that's where `nmsg-rpc` comes in.\r\n\r\nThis module has been extracted from [nmsg](http://npmjs.com/packages/nmsg)\r\nas a generic RPC router for any bi-directional messenger to create event-based routing.\r\n\r\n*Features:*\r\n - Send multiple callbacks using `.emit('event', cb1, 'data', cb2, cb3)`\r\n - Callbacks can be [nested arbitrarily deep](./examples/callbacks_nested.ts),\r\ni.e, your response can have callbacks inside them as well, and responses to reponse too, etc...\r\n - On the server, provides `rpc.Api` class where you define your API only\r\n once instead of adding events using `.on()` to every new connection.\r\n - Has no dependencies at ~350 lines of code.\r\n - See examples below for usage with `SockJS` and `socket.io`\r\n - Use [nmsg-rpc.js](./dist/nmsg-rpc.js) and [nmsg-rpc.min.js](./dist/nmsg-rpc.min.js) distributions that\r\n export this library as AMD or Node.js-compatible module and adds a global variable `nmsg.rpc` in your browser.\r\n\r\nCan be used with any socket that implements bi-directional communication like:\r\n\r\n```ts\r\ninterface ISocket {\r\n    onmessage: (msg: any) =\u003e void;\r\n    send(msg: any);\r\n}\r\n```\r\n\r\n(P.S. [`Websocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) is implemented like that.)\r\n\r\nUsage:\r\n\r\n```ts\r\nimport * as rpc from 'nmsg-rpc'; // var rpc = require('nmsg-rpc');\r\n\r\nvar socket: ISocket; // Socket that can send messages and receive messages.\r\nvar router = new rpc.Router;\r\n\r\n// Proxy the messages to your `router`.\r\nrouter.send = (obj) =\u003e { socket.send(obj); };\r\nsocket.onmessage = (obj) =\u003e { router.onmessage(obj); };\r\n```\r\n\r\nYou do this for both of your sockets: the server one and the client one. Now,\r\nyou can use your newly created `router` like so:\r\n\r\n```js\r\n// On server\r\nrouter.on('ping', function(callback) {\r\n    callback('pong');\r\n});\r\n\r\n// On client\r\nrouter.emit('ping', function(result) {\r\n    console.log(result); // pong\r\n});\r\n```\r\n\r\nYou can use wildcard `\"*\"` event to capture all imcoming messages:\r\n\r\n```ts\r\nrouter.on('*', function(event, ...args: any[]) {\r\n    // All incoming messages here.\r\n    // If any callbacks in `args` list, make sure you call it only once\r\n    // as this message will be passed to corresponding event callback as well.\r\n}); \r\n```\r\n\r\n## Reference\r\n\r\n### `rpc.Router`\r\n\r\n```ts\r\nclass Router {\r\n    send: (data) =\u003e void;\r\n    onmessage(msg: any): void;\r\n    onerror: (err) =\u003e void;\r\n    onevent: (event: string, args: any[]) =\u003e void;\r\n    setApi(api: Api): this;\r\n    on(event: string, callback: TeventCallback): this;\r\n    emit(event: string, ...args: any[]): this;\r\n}\r\n```\r\n\r\n - `.send(data)` -- you have to implement this function.\r\n - `.onmessage(msg)` -- you have to call this function when new messages arrive.\r\n - `.on()` and `.emit()` -- use these two methods to do all your communication between the processes.\r\n - `.onerror(err)` -- you can implement this function to listen for parsing errors.\r\n - `.onevent(event: string, args: any[])` -- implement this function to wiretap on all incoming events.\r\n\r\n### `rpc.RouterBuffered`\r\n\r\n`rpc.RouterBuffered` is almost the same as `rpc.Router` except it buffers all outgoing `.emit()` calls\r\nfor 10 milliseconds and then combines them into one bulk request and flushes it, thus combining many small\r\ncalls into one bigger request.\r\n\r\n### `rpc.Api`\r\n\r\nOn server side you actually don't want to add `.on()` event callbacks for every\r\nsocket. Imagine you had 1,000 `.on()` callbacks for each socket and 1,000\r\nlive sockets, you would need to create 1,000,000 functions for that.\r\n\r\nTo avoid that, server-side use `rpc.Api` class to define all your functions\r\nonly once like so:\r\n\r\n```ts\r\nvar api = new rpc.Api()\r\n    .add({\r\n        method1: function() { /*...*/ },\r\n        method2: function() { /*...*/ },\r\n    })\r\n    .add({\r\n        method3: function() { /*...*/ },\r\n    });\r\n    \r\nvar router = new rpc.Router;\r\nrouter.setApi(api);\r\n```\r\n\r\nMethods defined in `rpc.Api` will *\"overwrite\"* equally named events attached using `.on()`.\r\n\r\n*TypeScript* type definitions available in [./nmsg-rpc.d.ts](./nmsg-rpc.d.ts).\r\n\r\n## Examples\r\n\r\nOriginally `nmsg-rpc` was part of the [`nmsg`](http://npmjs.com/package/nmsg) project but it is so useful\r\non its own that we have carved it out into a standalone package. At only 344 lines of code (as of this writing)\r\nand no external dependencies, `nmsg-rpc` is lightweight enough for you to use in almost any project.\r\n\r\nBelow we will take at the following use cases:\r\n\r\n - Talking with a web `Worker`\r\n - Talking with `\u003ciframe\u003e` or other windows using `.postMessage()`\r\n - Using `window.localStorage` as a communication channel between browser tabs\r\n - Interface for `cluster`'s master thread and forked workers in Node.js\r\n - Adding event-base routing to `SockJS`\r\n - Improving routing for `socket.io`\r\n\r\nSee [./examples](./examples) folder for all the examples.\r\n \r\n### Talking with a web `Worker`\r\n\r\nWeb [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) allows you\r\nto do computations in a browser on a separate thread. It exposes `.postMessage()` method\r\nand `.onmessage` function property which you can use to communicate with your `Worker`.\r\n\r\nHere we use those to create `rpc.Router` for bi-directional event-based communication with callbacks between\r\nthe main thread and the worker.\r\n\r\nLet's start with the [`woker.js`](./examples/webworker/worker.js) script that implements our `Worker`. For your browser projects,\r\nyou can use builds of `nmsg-rpc` packaged in [nmsg-rpc.js](./dist/nmsg-rpc.js) and [nmsg-rpc.min.js](./dist/nmsg-rpc.min.js)\r\nfrom [./dist](./dist) folder.\r\n\r\n```js\r\n// We include our library, which will give use a global `nmsg` object.\r\nimportScripts('../../dist/nmsg-rpc.min.js');\r\n\r\n// Create our router using `Worker`'s `postMessage` and `onmessage` global properties.\r\nvar router = new nmsg.rpc.Router();\r\nrouter.send = postMessage.bind(this);\r\nonmessage = function(e) { router.onmessage(e.data); };\r\n\r\n// Now we can use `router.emit()` and `router.on()` functionality to define our API.\r\nrouter.on('calculate', function(code, callback) {\r\n    callback(eval(code));\r\n});\r\n```\r\n\r\nNow in the parent thread [`index.html`](./examples/webworker/index.html), we create this web `Worker`\r\nand talk to it using `rpc.Router`:\r\n\r\n```js\r\n// Create a web `Worker`.\r\nvar worker = new Worker('worker.js');\r\n\r\n// Create our `rpc.Router` to talk with the `Worker`.\r\nvar router = new nmsg.rpc.Router;\r\nrouter.send = worker.postMessage.bind(worker);\r\nworker.onmessage = function(e) { router.onmessage(e.data); };\r\n\r\n// Send messages to the `Worker`.\r\nrouter.emit('calculate', '1 + 1', function(result) {\r\n    console.log(result); // 2\r\n});\r\n```\r\n\r\nSee the [sample files](./examples/webworker).\r\n\r\n### `window.postMessage()` for communicating with `\u003ciframe/\u003e`\r\n\r\nFor security reasons `\u003ciframe/\u003e`s are sandboxed and the only communication\r\nmechanism with them is through `.postMessage()` method, obviously that\r\nway your messages are sent out of order and you have to keep track of them somehow to, for example,\r\ncreate a request/response functionality, we use `rpc.Router` to solve this.\r\n\r\nOne thing to remember is that `.postMessage()` method in some browsers can only send `string`\r\nmessages, so we use `JSON.stringify` and `JSON.parse` to serialize our objects.\r\n\r\nInside the [iframe.html](./examples/iframe/iframe.html) you can create a `router` object as follows:\r\n\r\n```js\r\n// Create our `rpc.Router` using \u003ciframe/\u003e's `.postMessage()` and `.onmessage` methods.\r\nvar router = new nmsg.rpc.Router;\r\nrouter.send = function(obj) {\r\n    window.top.postMessage(JSON.stringify(obj), '*');\r\n};\r\nwindow.addEventListener('message', function(event) {\r\n    router.onmessage(JSON.parse(event.data));\r\n});\r\n\r\n// Define our API.\r\nrouter.on('ping', function(callback) {\r\n    callback('pong');\r\n});\r\n```\r\n\r\nAnd in our [parent window](./examples/iframe/index.html) we do:\r\n\r\n```js\r\n// Get a reference to your \u003ciframe\u003e somehow.\r\nvar iframe = document.getElementById('iframe');\r\niframe.onload = function() { // Wait until \u003ciframe\u003e loads.\r\n    // Create `rpc.Router`.\r\n    var router = new nmsg.rpc.Router;\r\n    router.send = function(obj) {\r\n        iframe.contentWindow.postMessage(JSON.stringify(obj), '*');\r\n    };\r\n    window.addEventListener('message', function(event) {\r\n        router.onmessage(JSON.parse(event.data));\r\n    });\r\n\r\n    // Communicate with your \u003ciframe\u003e.\r\n    router.emit('ping', function(res) {\r\n        console.log('ping \u003e ' + res); // ping \u003e pong\r\n    });\r\n};\r\n```\r\n\r\nSee the [this example](./examples/iframe).\r\n\r\nNote that this is just an example for illustration purposes, as there are plenty of \r\nother things to consider when communicating with `\u003ciframe\u003e`s. For example, all windows can send messages\r\nto all other windows, so the example will work if you have only two windows. Also, when dealing\r\n`window.postMessage()` you must check the origin of the messages for security purposes, as any\r\nwindow can send messages to your script.\r\n\r\n### Intercom for browser tabs using `window.localStorage`\r\n\r\nHere we will create a messaging system between browser tabs, all just in few lines of code.\r\n\r\nYou can store data which is accessible by all browser tabs in `window.localStorage`,\r\nonce a key in `window.localStorage` is modified, `window` fires a `storage` event in \r\n*all other* tabs with the modified key event.\r\n\r\nWe use this functionality to send our messages by mutating some key on `localStorage` and\r\nwe listen to the `storage` event to capture incoming messages.\r\n\r\nThis is how we create our router in [router.js](./examples/tabs/router.js):\r\n\r\n```js\r\n// Create the `rpc.Router`.\r\nvar router = new nmsg.rpc.Router;\r\n\r\n// Send router frames using `window.localStorage` facility.\r\nrouter.send = function(obj) {\r\n    // The `storage` event on `Window` is fired only when contents of a key changes,\r\n    // so we make sure our key is removed at first.\r\n    window.localStorage.removeItem('intercom');\r\n    window.localStorage.setItem('intercom', JSON.stringify(obj));\r\n};\r\n\r\n// Receive messages using `storage` event listener,\r\n// which is fired every time `window.localStorage` is modified.\r\nwindow.addEventListener('storage', function (event) {\r\n    if((event.key == 'intercom') \u0026\u0026 (event.type == 'storage') \u0026\u0026 event.newValue) {\r\n        var obj = JSON.parse(event.newValue);\r\n        // HACK: add an extra argument to our listeners containing the `storage` event.\r\n        obj.a.push(event);\r\n        router.onmessage(obj);\r\n    }\r\n});\r\n```\r\n\r\nNow we create a [`tab.html`](./examples/tabs/tab.html) file that will communicate\r\nwith other opened tabs by sending a hello message and receiving a response from\r\nother tabs by whichever tab sends the response first:\r\n\r\n```js\r\nrouter.on('Hello, tab, how are you?', function(callback, event) {\r\n    console.log('Event:', event);\r\n    callback('Not bad! How are you?');\r\n});\r\n\r\nrouter.emit('Hello, tab, how are you?', function(response, event) {\r\n    console.log(response, event);\r\n});\r\n```\r\n\r\nIf you open [`tab.html`](./examples/tabs/tab.html) in one tab you should see nothing\r\nat first. Then you open that same file in one more tab and you should see in console:\r\n\r\n    Not bad! How are you? [Storage event object]\r\n    \r\nAnd now in the first tab this will appear:\r\n\r\n    Event: [Storage event object]\r\n    \r\nSee the full example [here](./examples/tabs).\r\n\r\n### Interface for Node.js `cluster`'s main thread and forked workers \r\n\r\nWe can use `rpc.Router` to create a communication interface for Node's\r\nmain process and its forked workers.\r\n\r\nThis is how we create an `rpc.Router` on the master thread:\r\n\r\n```js\r\nvar router = new rpc.Router;\r\nrouter.send = worker.send.bind(worker);\r\ncluster.on('message', function(obj) { router.onmessage(obj); });\r\n\r\n// Send message to a worker.\r\nrouter.emit('still alive?', function(response) {\r\n    console.log(response);\r\n});\r\n```\r\n\r\nAnd for forked workers we create a router like so:\r\n\r\n```js\r\nvar router = new rpc.Router;\r\nrouter.send = process.send.bind(process);\r\nprocess.on('message', function (obj) { router.onmessage(obj); });\r\n\r\nrouter.on('still alive?', function(callback) {\r\n    callback('Yes!');\r\n});\r\n```\r\n\r\nSee full example in [./examples/cluster](./examples/cluster).\r\n\r\n### Adding event-base routing to `SockJS`\r\n\r\nOut-of-the-box `SockJS` does not provide any sophisticated message routing \r\ninterface, but just `.onmessage` and `.send` methods. Conveniently those methods\r\nare just enough to create an `rpc.Router` wrapper around `SockJS`.\r\n\r\nThis is how we do it on the server:\r\n\r\n```js\r\nvar http = require('http');\r\nvar sockjs = require('sockjs');\r\nvar rpc = require('nmsg-rpc');\r\n\r\nvar ws = sockjs.createServer();\r\nws.on('connection', function(conn) {\r\n\r\n    // Wrap SockJS into our router.\r\n    var router = new rpc.Router;\r\n    conn.on('data', function(message) { router.onmessage(JSON.parse(message)); });\r\n    router.send = function(obj) { conn.write(JSON.stringify(obj)); };\r\n\r\n    // Create an echo method.\r\n    router.on('echo', function(msg, callback) {\r\n        callback(msg);\r\n    });\r\n});\r\n\r\nvar server = http.createServer();\r\nws.installHandlers(server, {prefix: '/ws'});\r\nserver.listen(9999, '127.0.0.1');\r\n```\r\n\r\nAnd this is how you do it in a browser:\r\n\r\n```js\r\nvar sock = new SockJS('http://127.0.0.1:9999/ws');\r\n\r\nsock.onopen = function() {\r\n    var router = new nmsg.rpc.Router;\r\n    router.send = function(obj) { sock.send(JSON.stringify(obj)); };\r\n    sock.onmessage = function(msg) { router.onmessage(JSON.parse(msg.data)); };\r\n\r\n    router.emit('echo', 'Hi server', function(response) {\r\n        console.log(response); // Hi server\r\n    });\r\n};\r\n```\r\n\r\nIn this example we create an `echo` event that just echoes back the original\r\ntext, so in your browser console, `Hi server` will appear. See [here](./examples/sockjs) full example.\r\n\r\n### Smart routing for `socket.io`\r\n\r\nWell `socket.io` has its own message routing mechanisms built in. However, it's\r\nrouting system in one of the most sophisticated and handicapped at the same.\r\n\r\nIt has advanced routing mechanisms like [Rooms and Namespaces](http://socket.io/docs/rooms-and-namespaces/#), which\r\nnot many understand how to use and almost none actually uses.\r\n\r\nAnd at the same time, it does not offer such simple functionality as a wildcard\r\n`\"*\"` event, for example, to catch all events. Also, it forces you to add\r\nall your event listeners using the `.on()` method to every new socket. So, for example,\r\nif you have 100 different event listeners and 100 sockets concurrently connected to\r\nthe sever, you would need to create 10,000 functions, instead of just having a\r\nset of 100 function which are the same for every connection anyways. And, of course,\r\n`socket.io` allows you to send a callback on your `.emit('event', 'data', cb)` call, but it allows\r\nyou to send only a single callback and only at the end of the argument list. `rpc.Router`\r\ndoes not have such limitations, you can have as many callbacks as you wish in any\r\nposition of `.emit()` argument list and even arbitrarily deeply nested callbacks in\r\nin your responses.\r\n\r\nYou can *fix* these limitations of `socket.io` by using it together with `nmsg-rpc`.\r\nIn the example below, we proxy messages using `proxy` event in and out of `rpc.Router`,\r\nwe also use `rpc.Api` object to define our API functions only once and share that object\r\nwith every new router object.\r\n\r\n```js\r\nvar io = require('socket.io')();\r\nvar rpc = require('nmsg-rpc');\r\n\r\n// Create API only once, instead of attaching gazillion `.on` events to EACH new socket.\r\nvar api = new rpc.Api;\r\napi.add({\r\n    echo: function(msg, callback) {\r\n        callback(msg);\r\n    }\r\n});\r\n\r\nio.on('connection', function(socket){\r\n    // Create a router which we will use instead of `socket.io`'s built-in one.\r\n    var router = new rpc.Router;\r\n\r\n    // Tell the router to use API we created once for all routers.\r\n    router.setApi(api);\r\n\r\n    // Proxy messages using `proxy` event to our new router.\r\n    router.send = function(obj) { socket.emit('proxy', obj); };\r\n    socket.on('proxy', function(obj) { router.onmessage(obj); });\r\n});\r\nio.listen(9999);\r\n```\r\n\r\nOn the client we just proxy all messages using `proxy` event to `rpc.Router` as well:\r\n\r\n```js\r\nvar socket = io('http://127.0.0.1:9999');\r\n\r\n// Route messages from `socket.io` to our router using arbitrary `proxy` event.\r\nvar router = new nmsg.rpc.Router;\r\nrouter.send = function(obj) { socket.emit('proxy', obj); };\r\nsocket.on('proxy', function(obj) { router.onmessage(obj); });\r\n\r\n// Get `Hello world` back from the server.\r\nsocket.on('connect', function(){\r\n    router.emit('echo', 'Hello world', function(response) {\r\n        console.log(response);\r\n    });\r\n});\r\n```\r\n\r\nYou can find this example [here](./examples/socket.io).\r\n\r\n## Developing\r\n\r\nGetting started:\r\n\r\n    npm run start\r\n\r\nTesting:\r\n\r\n    npm run test\r\n    \r\nGenerate `nmsg-rpc.d.ts` typing file:\r\n\r\n    npm run typing\r\n    \r\nPublishing:\r\n    \r\n    npm run mypublish\r\n    \r\nCreate a distribution files in [./dist](./dist) folder:\r\n\r\n    npm run dist\r\n    \r\n## License\r\n\r\nThis is free and unencumbered software released into the public domain.\r\n\r\nAnyone is free to copy, modify, publish, use, compile, sell, or\r\ndistribute this software, either in source code form or as a compiled\r\nbinary, for any purpose, commercial or non-commercial, and by any\r\nmeans.\r\n\r\nIn jurisdictions that recognize copyright laws, the author or authors\r\nof this software dedicate any and all copyright interest in the\r\nsoftware to the public domain. We make this dedication for the benefit\r\nof the public at large and to the detriment of our heirs and\r\nsuccessors. We intend this dedication to be an overt act of\r\nrelinquishment in perpetuity of all present and future rights to this\r\nsoftware under copyright law.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\r\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\nOTHER DEALINGS IN THE SOFTWARE.\r\n\r\nFor more information, please refer to \u003chttp://unlicense.org/\u003e\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstreamich%2Fnmsg-rpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstreamich%2Fnmsg-rpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstreamich%2Fnmsg-rpc/lists"}