├── magic.gif ├── rawr.jpg ├── .coveralls.yml ├── .eslintignore ├── global.js ├── examples ├── mqtt │ ├── server.js │ ├── README.md │ ├── package.json │ ├── client-a.js │ └── client-b.js ├── websocket │ ├── README.md │ ├── public │ │ └── index.html │ ├── package.json │ ├── client.js │ └── server.js └── webworker │ ├── README.md │ ├── public │ └── index.html │ ├── worker.js │ ├── package.json │ └── main.js ├── .gitignore ├── transports ├── index.js ├── socketio │ └── index.js ├── mqtt │ └── index.js ├── websocket │ └── index.js └── worker │ └── index.js ├── .github └── workflows │ └── workflow.yml ├── .eslintrc ├── LICENSE ├── package.json ├── test ├── websocket_transport.js ├── worker_transport.js ├── socketio_transport.js ├── mqtt_transport.js └── index.js ├── README.md ├── index.js └── dist └── bundle.js /magic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceddev/rawr/HEAD/magic.gif -------------------------------------------------------------------------------- /rawr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceddev/rawr/HEAD/rawr.jpg -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: circleci 2 | repo_token: KqreYWh2mPceG9bZOGcYnkkVtpt4FwcJU 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | coverage/ 4 | examples/webworker/public 5 | examples/websocket/public 6 | -------------------------------------------------------------------------------- /global.js: -------------------------------------------------------------------------------- 1 | const rawr = require('./'); 2 | const EventEmitter = require('eventemitter3'); 3 | rawr.EventEmitter = EventEmitter; 4 | 5 | globalThis.Rawr = rawr; 6 | 7 | module.exports = rawr; 8 | -------------------------------------------------------------------------------- /examples/mqtt/server.js: -------------------------------------------------------------------------------- 1 | const aedes = require('aedes')(); 2 | const server = require('net').createServer(aedes.handle); 3 | 4 | const port = 1883; 5 | 6 | server.listen(port, () => { 7 | console.log('mqtt server listening on port', port); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/websocket/README.md: -------------------------------------------------------------------------------- 1 | # using rawr over websockets example 2 | 3 | 4 | ## install deps 5 | 6 | `npm i` 7 | 8 | ## start 9 | 10 | `npm run start` 11 | 12 | ## open browser 13 | 14 | [http://localhost:8080](http://localhost:8080) 15 | -------------------------------------------------------------------------------- /examples/webworker/README.md: -------------------------------------------------------------------------------- 1 | # using rawr with a webworker example 2 | 3 | 4 | ## install deps 5 | 6 | `npm i` 7 | 8 | ## start 9 | 10 | `npm run start` 11 | 12 | ## open browser 13 | 14 | [http://localhost:8081](http://localhost:8081) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # Deployed apps should consider commenting this line out: 3 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 4 | node_modules 5 | package-lock.json 6 | 7 | # Other 8 | .DS_Store 9 | coverage 10 | -------------------------------------------------------------------------------- /transports/index.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('./mqtt'); 2 | const socketio = require('./socketio'); 3 | const websocket = require('./websocket'); 4 | const worker = require('./worker'); 5 | 6 | module.exports = { 7 | mqtt, 8 | socketio, 9 | websocket, 10 | worker 11 | }; -------------------------------------------------------------------------------- /examples/mqtt/README.md: -------------------------------------------------------------------------------- 1 | # using rawr with mqtt example 2 | 3 | 4 | ## install deps 5 | 6 | `npm i` 7 | 8 | ## start the mqtt server 9 | 10 | `npm run server` 11 | 12 | ## start client-a 13 | 14 | `npm run client-a` 15 | 16 | ## start client-b 17 | 18 | `npm run client-b` 19 | -------------------------------------------------------------------------------- /examples/webworker/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | + = 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/websocket/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | + = 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/webworker/worker.js: -------------------------------------------------------------------------------- 1 | const rawr = require('../../'); 2 | 3 | function add(x, y) { 4 | return x + y; 5 | } 6 | 7 | // create the rawr peer 8 | const rawrPeer = rawr({ transport: rawr.transports.worker(), handlers: { add } }); 9 | 10 | // make RPC calls to the DOM 11 | setInterval(async () => { 12 | const val = await rawrPeer.methods.getRandom(); 13 | console.log('random from DOM', val); 14 | }, 1000); 15 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | with: 9 | fetch-depth: 1 10 | - name: Setup Node.js 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: 14.15.1 14 | - name: Installing dependencies 15 | run: npm install 16 | - name: Running tests 17 | run: npm test 18 | -------------------------------------------------------------------------------- /examples/mqtt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawr-mqtt-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "server": "node server", 9 | "client-a": "node client-a", 10 | "client-b": "node client-b" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "aedes": "^0.37.0", 16 | "mqtt": "^2.18.8" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /transports/socketio/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('eventemitter3'); 2 | 3 | function transport({ connection, subTopic, pubTopic }) { 4 | const emitter = new EventEmitter(); 5 | connection.on(subTopic, (msg) => { 6 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 7 | emitter.emit('rpc', msg); 8 | } 9 | }); 10 | emitter.send = (msg) => { 11 | connection.emit(pubTopic, msg); 12 | }; 13 | return emitter; 14 | } 15 | 16 | module.exports = transport; 17 | -------------------------------------------------------------------------------- /examples/websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawr-websocket-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "browserify client.js -o public/client-bundle.js", 8 | "start": "npm run build && node server" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "browserify": "^14.4.0", 14 | "express": "^4.15.4", 15 | "rawr": "^0.7.0", 16 | "ws": "^3.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/webworker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawr-webworker-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify main.js -o public/main-bundle.js && browserify worker.js -o public/worker-bundle.js", 9 | "start": "npm run build && ecstatic ./public --port 8081" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "browserify": "^14.4.0", 15 | "ecstatic": "^3.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/mqtt/client-a.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('mqtt'); 2 | const rawr = require('../../'); 3 | 4 | const connection = mqtt.connect('mqtt://localhost'); 5 | 6 | function add(x, y) { 7 | return x + y; 8 | } 9 | 10 | // create the rawr peer 11 | const rawrPeer = rawr({ 12 | transport: rawr.transports.mqtt({ connection, pubTopic: 'client-b', subTopic: 'client-a' }), 13 | handlers: { add }, 14 | timeout: 1000 15 | }); 16 | 17 | // make RPC calls to the DOM 18 | setInterval(async () => { 19 | try { 20 | const val = await rawrPeer.methods.getRandom(); 21 | console.log('result sent from client-b', val); 22 | } catch (error) { 23 | console.log('error calling client-b', error.message); 24 | } 25 | }, 1000); 26 | -------------------------------------------------------------------------------- /examples/mqtt/client-b.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('mqtt'); 2 | const rawr = require('../../'); 3 | 4 | const connection = mqtt.connect('mqtt://localhost'); 5 | 6 | function getRandom() { 7 | return Math.random(); 8 | } 9 | 10 | // create the rawr peer 11 | const rawrPeer = rawr({ 12 | transport: rawr.transports.mqtt({ connection, pubTopic: 'client-a', subTopic: 'client-b' }), 13 | handlers: { getRandom }, 14 | timeout: 1000 15 | }); 16 | 17 | // make RPC calls to the DOM 18 | setInterval(async () => { 19 | try { 20 | const val = await rawrPeer.methods.add(1, 3); 21 | console.log('result sent from client-a', val); 22 | } catch (error) { 23 | console.log('error calling client-a', error.message); 24 | } 25 | }, 1000); 26 | -------------------------------------------------------------------------------- /examples/webworker/main.js: -------------------------------------------------------------------------------- 1 | const rawr = require('../../'); 2 | 3 | const myWorker = new Worker('/worker-bundle.js'); 4 | 5 | // create the rawr peer 6 | const rawPeer = rawr({ transport: rawr.transports.worker(myWorker) }); 7 | 8 | // handle requests from the webworker 9 | rawPeer.addHandler('getRandom', () => Math.random()); 10 | 11 | // make an RPC call to the webworker server on a button click 12 | document.getElementById('addBtn').addEventListener('click', async () => { 13 | const num1 = parseFloat(document.getElementById('number1').value); 14 | const num2 = parseFloat(document.getElementById('number2').value); 15 | const result = await rawPeer.methods.add(num1, num2); 16 | document.getElementById('result').innerHTML = result; 17 | }, false); 18 | -------------------------------------------------------------------------------- /examples/websocket/client.js: -------------------------------------------------------------------------------- 1 | const rawr = require('../../'); 2 | 3 | const ws = new WebSocket('ws://localhost:8080'); 4 | 5 | ws.onopen = () => { 6 | // create the rawr peer 7 | const rawPeer = rawr({ transport: rawr.transports.websocket(ws) }); 8 | 9 | // handle requests from the websocket server 10 | rawPeer.addHandler('getRandom', () => Math.random()); 11 | 12 | // make an RPC call to the websocket server on a button click 13 | document.getElementById('addBtn').addEventListener('click', async () => { 14 | const num1 = parseFloat(document.getElementById('number1').value); 15 | const num2 = parseFloat(document.getElementById('number2').value); 16 | const result = await rawPeer.methods.add(num1, num2); 17 | document.getElementById('result').innerHTML = result; 18 | }, false); 19 | }; 20 | -------------------------------------------------------------------------------- /transports/mqtt/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('eventemitter3'); 2 | 3 | function transport({ connection, subTopic, pubTopic, subscribe = true }) { 4 | const emitter = new EventEmitter(); 5 | if (subscribe) { 6 | connection.subscribe(subTopic); 7 | } 8 | connection.on('message', (topic, message) => { 9 | if (topic === subTopic) { 10 | try { 11 | const msg = JSON.parse(message.toString()); 12 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 13 | emitter.emit('rpc', msg); 14 | } 15 | } catch (err) { 16 | console.error(err); 17 | } 18 | } 19 | }); 20 | emitter.send = (msg) => { 21 | connection.publish(pubTopic, JSON.stringify(msg)); 22 | }; 23 | return emitter; 24 | } 25 | 26 | module.exports = transport; 27 | -------------------------------------------------------------------------------- /transports/websocket/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('eventemitter3'); 2 | 3 | function transport(socket, allowBinary = false) { 4 | const emitter = new EventEmitter(); 5 | socket.addEventListener('message', async (evt) => { 6 | let { data } = evt; 7 | if (allowBinary && data instanceof Blob) { 8 | data = await (new Response(data)).text().catch(() => null); 9 | } 10 | if (typeof evt.data === 'string') { 11 | try { 12 | const msg = JSON.parse(evt.data); 13 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 14 | emitter.emit('rpc', msg); 15 | } 16 | } catch (err) { 17 | // wasn't a JSON message 18 | } 19 | } 20 | }); 21 | emitter.send = (msg) => { 22 | socket.send(JSON.stringify(msg)); 23 | }; 24 | return emitter; 25 | } 26 | 27 | module.exports = transport; 28 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // enable airbnb eslint from https://www.npmjs.com/package/eslint-config-airbnb 3 | "extends": "airbnb", 4 | "rules": { 5 | "consistent-return": [0, { "treatUndefinedAsUnspecified": true }], 6 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 7 | "no-multi-assign": "off", 8 | "no-console": "off", 9 | "no-plusplus": "off", 10 | "camelcase": "off", 11 | "comma-dangle": "off", 12 | "prefer-template": "off", 13 | "object-curly-newline": "off", 14 | "no-restricted-globals": "off", // WTF? `self` should be fine 15 | "quotes": [ "error", "single", { "allowTemplateLiterals": true}], 16 | "arrow-body-style": "off", 17 | "import/no-unresolved": "off", 18 | "max-len": [2, 120, 2] 19 | }, 20 | "env": { 21 | "browser": true, 22 | "node": true, 23 | "worker": true 24 | }, 25 | "globals": { 26 | "it": "readonly", 27 | "describe": "readonly" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/websocket/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const http = require('http'); 3 | const WebSocket = require('ws'); 4 | const rawr = require('../../'); 5 | 6 | const app = express(); 7 | app.use(express.static('public')); 8 | 9 | const server = http.createServer(app); 10 | const wss = new WebSocket.Server({ server }); 11 | 12 | function add(x, y) { 13 | return x + y; 14 | } 15 | 16 | wss.on('connection', (socket) => { 17 | const rawrPeer = rawr({ transport: rawr.transports.websocket(socket) }); 18 | rawrPeer.addHandler('add', add); 19 | 20 | // make RPC calls to the client 21 | const intervalId = setInterval(async () => { 22 | const val = await rawrPeer.methods.getRandom(); 23 | console.log('random from client', val); 24 | }, 1000); 25 | 26 | // cleanup 27 | socket.on('close', () => { 28 | console.log('disconnected'); 29 | clearInterval(intervalId); 30 | }); 31 | }); 32 | 33 | server.listen(8080, () => { 34 | console.log('Listening on %d', server.address().port); 35 | }); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Iced Development, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /transports/worker/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('eventemitter3'); 2 | 3 | function dom(webWorker) { 4 | const emitter = new EventEmitter(); 5 | webWorker.addEventListener('message', (msg) => { 6 | const { data } = msg; 7 | if (data && (data.method || (data.id && ('result' in data || 'error' in data)))) { 8 | emitter.emit('rpc', data); 9 | } 10 | }); 11 | emitter.send = (msg, config) => { 12 | webWorker.postMessage(msg, config ? config.postMessageOptions : undefined); 13 | }; 14 | return emitter; 15 | } 16 | 17 | function worker() { 18 | const emitter = new EventEmitter(); 19 | self.onmessage = (msg) => { 20 | const { data } = msg; 21 | if (data && (data.method || (data.id && ('result' in data || 'error' in data)))) { 22 | emitter.emit('rpc', data); 23 | } 24 | }; 25 | emitter.send = (msg, config) => { 26 | self.postMessage(msg, config ? config.postMessageOptions : undefined); 27 | }; 28 | return emitter; 29 | } 30 | 31 | function transport(webWorker) { 32 | if (webWorker) { 33 | return dom(webWorker); 34 | } 35 | return worker(); 36 | } 37 | 38 | // backwards compat 39 | transport.dom = dom; 40 | transport.worker = worker; 41 | 42 | module.exports = transport; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawr", 3 | "version": "0.19.0", 4 | "description": "JSON-RPC over simple node style event emitters", 5 | "dependencies": { 6 | "eventemitter3": "^5.0.1" 7 | }, 8 | "devDependencies": { 9 | "b64id": "^1.0.1", 10 | "browserify": "^16.2.3", 11 | "chai": "^4.2.0", 12 | "coveralls": "^3.0.3", 13 | "eslint": "^5.15.3", 14 | "eslint-config-airbnb": "^17.1.0", 15 | "eslint-plugin-import": "^2.16.0", 16 | "eslint-plugin-jsx-a11y": "^6.2.1", 17 | "eslint-plugin-react": "^7.12.4", 18 | "istanbul": "^0.4.5", 19 | "mocha": "^10.2.0" 20 | }, 21 | "main": "index.js", 22 | "scripts": { 23 | "lint": "eslint ./index.js --ext .js", 24 | "test": "npm run lint && istanbul cover _mocha && npm run check-coverage", 25 | "mocha": "_mocha", 26 | "build": "browserify global.js -o dist/bundle.js", 27 | "check-coverage": "istanbul check-coverage --statements 100 --branches 75 --lines 100 --functions 100", 28 | "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/iceddev/rawr" 33 | }, 34 | "keywords": [ 35 | "rpc", 36 | "json-rpc", 37 | "promises", 38 | "websocket", 39 | "ws", 40 | "promise", 41 | "mqtt" 42 | ], 43 | "author": "Luis Montes (http://iceddev.com/)", 44 | "license": "MIT", 45 | "readmeFilename": "README.md", 46 | "bugs": { 47 | "url": "https://github.com/iceddev/rawr/issues" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/websocket_transport.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const EventEmitter = require('eventemitter3'); 3 | const rawr = require('../'); 4 | 5 | chai.should(); 6 | 7 | function mockTransports() { 8 | const a = new EventEmitter(); 9 | const b = new EventEmitter(); 10 | 11 | a.send = (msg) => { 12 | b.emit('message', {data: msg}); 13 | }; 14 | a.addEventListener = (topic, cb) => { 15 | a.on(topic, cb); 16 | } 17 | 18 | b.send = (msg) => { 19 | a.emit('message', {data: msg}); 20 | }; 21 | b.addEventListener = (topic, cb) => { 22 | b.on(topic, cb); 23 | } 24 | 25 | const transportA = rawr.transports.websocket(a); 26 | const transportB = rawr.transports.websocket(b); 27 | 28 | return { transportA, transportB }; 29 | } 30 | 31 | function helloTest(name) { 32 | return new Promise((resolve, reject) => { 33 | if (name === 'bad') { 34 | const error = new Error('bad name !'); 35 | error.code = 9000; 36 | return reject(error); 37 | } 38 | setTimeout(() => { 39 | return resolve(`hello, ${name}`); 40 | }, 100); 41 | }); 42 | } 43 | 44 | function add(a, b) { 45 | return a + b; 46 | } 47 | 48 | function subtract(a, b) { 49 | return a - b; 50 | } 51 | 52 | describe('websocket', () => { 53 | it('should make a client', (done) => { 54 | const { transportA } = mockTransports(); 55 | const client = rawr({ transport: transportA }); 56 | client.should.be.a('object'); 57 | client.addHandler.should.be.a('function'); 58 | done(); 59 | }); 60 | 61 | it('client should make a successful rpc call to another peer', async () => { 62 | const { transportA, transportB } = mockTransports(); 63 | const clientA = rawr({ transport: transportA, handlers: { add } }); 64 | const clientB = rawr({ transport: transportB, handlers: { subtract } }); 65 | 66 | const resultA = await clientA.methods.subtract(7, 2); 67 | const resultB = await clientB.methods.add(1, 2); 68 | resultA.should.equal(5); 69 | resultB.should.equal(3); 70 | }); 71 | 72 | it('client should make an unsuccessful rpc call to a peer', async () => { 73 | const { transportA, transportB } = mockTransports(); 74 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 75 | const clientB = rawr({ transport: transportB }); 76 | 77 | clientA.should.be.an('object'); 78 | try { 79 | await clientB.methods.helloTest('bad'); 80 | } catch (error) { 81 | error.code.should.equal(9000); 82 | } 83 | }); 84 | 85 | it('client handle an rpc under a specified timeout', async () => { 86 | const { transportA, transportB } = mockTransports(); 87 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 88 | const clientB = rawr({ transport: transportB, timeout: 1000 }); 89 | 90 | clientA.should.be.an('object'); 91 | const result = await clientB.methods.helloTest('luis'); 92 | result.should.equal('hello, luis'); 93 | }); 94 | 95 | it('client handle an rpc timeout', async () => { 96 | const { transportA, transportB } = mockTransports(); 97 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 98 | const clientB = rawr({ transport: transportB, timeout: 10 }); 99 | 100 | clientA.should.be.an('object'); 101 | try { 102 | await clientB.methods.helloTest('luis'); 103 | } catch (error) { 104 | error.code.should.equal(504); 105 | } 106 | }); 107 | 108 | it('client should be able to send a notification to a server', (done) => { 109 | const { transportA, transportB } = mockTransports(); 110 | const clientA = rawr({ transport: transportA }); 111 | const clientB = rawr({ transport: transportB }); 112 | 113 | clientA.notifications.ondoSomething((someData) => { 114 | someData.should.equal('testing_notification'); 115 | done(); 116 | }); 117 | 118 | clientB.notifiers.doSomething('testing_notification'); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/worker_transport.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const EventEmitter = require('eventemitter3'); 3 | const rawr = require('../'); 4 | 5 | chai.should(); 6 | 7 | function mockTransports() { 8 | const fakeWorkerRef = new EventEmitter(); //in the dom 9 | const fakeWorkerInstance = new EventEmitter(); // the worker thread 10 | 11 | fakeWorkerInstance.postMessage = (msg) => { 12 | fakeWorkerRef.emit('message', {data: msg}); 13 | }; 14 | 15 | fakeWorkerRef.postMessage = (msg) => { 16 | fakeWorkerInstance.onmessage({data: msg}); 17 | }; 18 | 19 | fakeWorkerRef.addEventListener = (topic, cb) => { 20 | fakeWorkerRef.on(topic, cb); 21 | }; 22 | 23 | const transportA = rawr.transports.worker(fakeWorkerRef); 24 | global.self = fakeWorkerInstance; 25 | const transportB = rawr.transports.worker(); 26 | 27 | return { transportA, transportB }; 28 | } 29 | 30 | function helloTest(name) { 31 | return new Promise((resolve, reject) => { 32 | if (name === 'bad') { 33 | const error = new Error('bad name !'); 34 | error.code = 9000; 35 | return reject(error); 36 | } 37 | setTimeout(() => { 38 | return resolve(`hello, ${name}`); 39 | }, 100); 40 | }); 41 | } 42 | 43 | function add(a, b) { 44 | return a + b; 45 | } 46 | 47 | function subtract(a, b) { 48 | return a - b; 49 | } 50 | 51 | describe('worker', () => { 52 | it('should make a client', (done) => { 53 | const { transportA } = mockTransports(); 54 | const client = rawr({ transport: transportA }); 55 | client.should.be.a('object'); 56 | client.addHandler.should.be.a('function'); 57 | done(); 58 | }); 59 | 60 | it('client should make a successful rpc call to another peer', async () => { 61 | const { transportA, transportB } = mockTransports(); 62 | const clientA = rawr({ transport: transportA, handlers: { add } }); 63 | const clientB = rawr({ transport: transportB, handlers: { subtract } }); 64 | 65 | const resultA = await clientA.methods.subtract(7, 2); 66 | const resultB = await clientB.methods.add(1, 2); 67 | resultA.should.equal(5); 68 | resultB.should.equal(3); 69 | }); 70 | 71 | it('client should make an unsuccessful rpc call to a peer', async () => { 72 | const { transportA, transportB } = mockTransports(); 73 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 74 | const clientB = rawr({ transport: transportB }); 75 | 76 | clientA.should.be.an('object'); 77 | try { 78 | await clientB.methods.helloTest('bad'); 79 | } catch (error) { 80 | error.code.should.equal(9000); 81 | } 82 | }); 83 | 84 | it('client handle an rpc under a specified timeout', async () => { 85 | const { transportA, transportB } = mockTransports(); 86 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 87 | const clientB = rawr({ transport: transportB, timeout: 1000 }); 88 | 89 | clientA.should.be.an('object'); 90 | const result = await clientB.methods.helloTest('luis'); 91 | result.should.equal('hello, luis'); 92 | }); 93 | 94 | it('client handle an rpc timeout', async () => { 95 | const { transportA, transportB } = mockTransports(); 96 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 97 | const clientB = rawr({ transport: transportB, timeout: 10 }); 98 | 99 | clientA.should.be.an('object'); 100 | try { 101 | await clientB.methods.helloTest('luis'); 102 | } catch (error) { 103 | error.code.should.equal(504); 104 | } 105 | }); 106 | 107 | it('client should be able to send a notification to a server', (done) => { 108 | const { transportA, transportB } = mockTransports(); 109 | const clientA = rawr({ transport: transportA }); 110 | const clientB = rawr({ transport: transportB }); 111 | 112 | clientA.notifications.ondoSomething((someData) => { 113 | someData.should.equal('testing_notification'); 114 | done(); 115 | }); 116 | 117 | clientB.notifiers.doSomething('testing_notification'); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/socketio_transport.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const EventEmitter = require('eventemitter3'); 3 | const rawr = require('../'); 4 | 5 | chai.should(); 6 | 7 | function mockTransports() { 8 | const a = new EventEmitter(); 9 | const b = new EventEmitter(); 10 | 11 | a.originalEmit = a.emit; 12 | a.emit = (topic, msg) => { 13 | if(topic === 'aPub') { 14 | b.emit(topic, msg); 15 | } else { 16 | a.originalEmit(topic, msg); 17 | } 18 | }; 19 | 20 | b.originalEmit = b.emit; 21 | b.emit = (topic, msg) => { 22 | if(topic === 'bPub') { 23 | a.emit(topic, msg); 24 | } else { 25 | b.originalEmit(topic, msg); 26 | } 27 | }; 28 | 29 | const transportA = rawr.transports.socketio({ 30 | connection: a, 31 | pubTopic: 'aPub', 32 | subTopic: 'bPub' 33 | }); 34 | const transportB = rawr.transports.socketio({ 35 | connection: b, 36 | pubTopic: 'bPub', 37 | subTopic: 'aPub' 38 | }); 39 | 40 | return { transportA, transportB }; 41 | } 42 | 43 | function helloTest(name) { 44 | return new Promise((resolve, reject) => { 45 | if (name === 'bad') { 46 | const error = new Error('bad name !'); 47 | error.code = 9000; 48 | return reject(error); 49 | } 50 | setTimeout(() => { 51 | return resolve(`hello, ${name}`); 52 | }, 100); 53 | }); 54 | } 55 | 56 | function add(a, b) { 57 | return a + b; 58 | } 59 | 60 | function subtract(a, b) { 61 | return a - b; 62 | } 63 | 64 | describe('socketio', () => { 65 | it('should make a client', (done) => { 66 | const { transportA } = mockTransports(); 67 | const client = rawr({ transport: transportA }); 68 | client.should.be.a('object'); 69 | client.addHandler.should.be.a('function'); 70 | done(); 71 | }); 72 | 73 | it('client should make a successful rpc call to another peer', async () => { 74 | const { transportA, transportB } = mockTransports(); 75 | const clientA = rawr({ transport: transportA, handlers: { add } }); 76 | const clientB = rawr({ transport: transportB, handlers: { subtract } }); 77 | 78 | const resultA = await clientA.methods.subtract(7, 2); 79 | const resultB = await clientB.methods.add(1, 2); 80 | resultA.should.equal(5); 81 | resultB.should.equal(3); 82 | }); 83 | 84 | it('client should make an unsuccessful rpc call to a peer', async () => { 85 | const { transportA, transportB } = mockTransports(); 86 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 87 | const clientB = rawr({ transport: transportB }); 88 | 89 | clientA.should.be.an('object'); 90 | try { 91 | await clientB.methods.helloTest('bad'); 92 | } catch (error) { 93 | error.code.should.equal(9000); 94 | } 95 | }); 96 | 97 | it('client handle an rpc under a specified timeout', async () => { 98 | const { transportA, transportB } = mockTransports(); 99 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 100 | const clientB = rawr({ transport: transportB, timeout: 1000 }); 101 | 102 | clientA.should.be.an('object'); 103 | const result = await clientB.methods.helloTest('luis'); 104 | result.should.equal('hello, luis'); 105 | }); 106 | 107 | it('client handle an rpc timeout', async () => { 108 | const { transportA, transportB } = mockTransports(); 109 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 110 | const clientB = rawr({ transport: transportB, timeout: 10 }); 111 | 112 | clientA.should.be.an('object'); 113 | try { 114 | await clientB.methods.helloTest('luis'); 115 | } catch (error) { 116 | error.code.should.equal(504); 117 | } 118 | }); 119 | 120 | it('client should be able to send a notification to a server', (done) => { 121 | const { transportA, transportB } = mockTransports(); 122 | const clientA = rawr({ transport: transportA }); 123 | const clientB = rawr({ transport: transportB }); 124 | 125 | clientA.notifications.ondoSomething((someData) => { 126 | someData.should.equal('testing_notification'); 127 | done(); 128 | }); 129 | 130 | clientB.notifiers.doSomething('testing_notification'); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rawr (a.k.a. RAWRpc) 2 | 3 | [![NPM](https://nodei.co/npm/rawr.svg)](https://nodei.co/npm/rawr/) ![example workflow](https://github.com/iceddev/rawr/actions/workflows/workflow.yml/badge.svg) 4 | 5 | Remote Procedure Calls ([JSON-RPC](https://www.jsonrpc.org/specification)) sent over any [EventEmitter](https://nodejs.org/dist/latest-v12.x/docs/api/events.html#events_class_eventemitter)-based transport. [WebWorkers](/transports/worker), [WebSockets](/transports/websocket), [MQTT](/transports/mqtt), and more! 6 | 7 | ![RAWRpc](rawr.jpg) 8 | 9 | ## Installation 10 | 11 | `npm install rawr` 12 | 13 | 14 | ## Using rawr with a webworker 15 | 16 | Every rawr peer can act as both a client and a server, and make remote method calls in either direction. 17 | 18 | For example, we can use methods that belong to a webworker. 19 | 20 | #### In our worker.js file: 21 | ```javascript 22 | import rawr, { transports } from 'rawr'; 23 | 24 | // In this instantiation, we can pass in an object to 25 | // `methods` that is exposed to our web page (see below) 26 | const peer = rawr({ 27 | transport: transports.worker(), 28 | methods: { calculatePrimes }, 29 | }); 30 | 31 | function calculatePrimes(howMany) { 32 | // Do something CPU intensive in this thread that 33 | // would otherwise be too expensive for our web page 34 | ... 35 | return primes; 36 | } 37 | ``` 38 | 39 | #### In our web page: 40 | ```javascript 41 | import rawr, { transports } from 'rawr'; 42 | 43 | const myWorker = new Worker('/worker.js'); 44 | const peer = rawr({transport: transports.worker(myWorker)}); 45 | 46 | // Remote methods are *~automatically available~* 47 | const result = await peer.methods.calculatePrimes(349582); 48 | ``` 49 | 50 | The methods are available to the rawr peer through the magic of [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 51 | 52 | ![Magic](magic.gif) 53 | 54 | ## Using rawr with a websocket 55 | 56 | We could use rawr to make calls to a remote server such as a websocket. 57 | Simply use a different transport. 58 | 59 | #### on our web page: 60 | ```javascript 61 | import rawr, { transports } from 'rawr'; 62 | 63 | const socket = new WebSocket('ws://localhost:8080'); 64 | 65 | socket.onopen = (event) => { 66 | // create the rawr peer 67 | const peer = rawr({ 68 | transport: transports.websocket(socket) 69 | }); 70 | }; 71 | ``` 72 | 73 | The websocket server could even make *arbitrary calls to the client!* 74 | 75 | #### on the server: 76 | ```javascript 77 | socketServer.on('connection', (socket) => { 78 | const peer = rawr({ 79 | transport: transports.websocket(socket) 80 | }); 81 | 82 | const result = await peer.methods.doSomethingOnClient(); 83 | }); 84 | ``` 85 | 86 | ## Handling Notifications 87 | 88 | Peers can also send each other [notifications](https://www.jsonrpc.org/specification#notification): 89 | 90 | ```javascript 91 | peer.notifiers.saySomething('hello'); 92 | ``` 93 | 94 | Receiving those notifications from another peer is just as simple: 95 | ```javascript 96 | peer.notifications.onsaySomething((words) => { 97 | console.log(words); //hello 98 | }); 99 | ``` 100 | 101 | 102 | ## Transports 103 | 104 | Transporst are simply [EventEmitters](https://nodejs.org/dist/latest-v12.x/docs/api/events.html#events_class_eventemitter) that do two things: 105 | 106 | They emit ([json-rpc](https://www.jsonrpc.org/specification)) objects on an `rpc` topic when receiving data. 107 | ```javascript 108 | transport.emit('rpc', {jsonrpc:'2.0', id: 1, method: 'add', params: [2, 3]}); 109 | ``` 110 | 111 | They send rpc objects out. 112 | ```javascript 113 | transport.send({jsonrpc:'2.0', id: 1, method: 'subtract', params: [5, 4]}); 114 | ``` 115 | 116 | While, websockets, mqtt, and webworkers are common, transports could be built from any form of communication you wish! 117 | 118 | 119 | ## Custom Configuration for Method invocations 120 | 121 | if you need to pass configuration specific method invocations, you can uses the `methodsExt` property of a rawr instance. 122 | 123 | For example, if you want to specify a specific timeout for a method call you can use a configuration object as the last parameter: 124 | ```javascript 125 | try { 126 | const result = await peer.methodsExt.doSomething(a, b, { timeout: 100 }); 127 | } catch(e) { 128 | // method took longer than a 100 millseconds 129 | } 130 | ``` 131 | 132 | This also works for customizaton of the transport. 133 | For example, you may want to pass configuration for transferable objects to a webWorker: 134 | ```javascript 135 | const result = await peer.methodsExt.processImage({ imageBitmap, stuff }, { 136 | postMessageOptions: { transfer: [imageBitmap] } 137 | }); 138 | ``` 139 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('eventemitter3'); 2 | const transports = require('./transports'); 3 | 4 | function rawr({ transport, timeout = 0, handlers = {}, methods, idGenerator }) { 5 | let callId = 0; 6 | // eslint-disable-next-line no-param-reassign 7 | methods = methods || handlers || {}; // backwards compat 8 | const pendingCalls = {}; 9 | const methodHandlers = {}; 10 | const notificationEvents = new EventEmitter(); 11 | notificationEvents.on = notificationEvents.on.bind(notificationEvents); 12 | 13 | transport.on('rpc', (msg) => { 14 | if (msg.id) { 15 | // handle an RPC request 16 | if (msg.params && methodHandlers[msg.method]) { 17 | methodHandlers[msg.method](msg); 18 | return; 19 | } 20 | // handle an RPC result 21 | const promise = pendingCalls[msg.id]; 22 | if (promise) { 23 | if (promise.timeoutId) { 24 | clearTimeout(promise.timeoutId); 25 | } 26 | delete pendingCalls[msg.id]; 27 | if (msg.error) { 28 | promise.reject(msg.error); 29 | } 30 | return promise.resolve(msg.result); 31 | } 32 | return; 33 | } 34 | // handle a notification 35 | msg.params.unshift(msg.method); 36 | notificationEvents.emit(...msg.params); 37 | }); 38 | 39 | function addHandler(methodName, handler) { 40 | methodHandlers[methodName] = (msg) => { 41 | Promise.resolve() 42 | .then(() => { 43 | return handler.apply(this, msg.params); 44 | }) 45 | .then((result) => { 46 | transport.send({ 47 | id: msg.id, 48 | result 49 | }); 50 | }) 51 | .catch((error) => { 52 | const serializedError = { message: error.message }; 53 | if (error.code) { 54 | serializedError.code = error.code; 55 | } 56 | transport.send({ 57 | id: msg.id, 58 | error: serializedError 59 | }); 60 | }); 61 | }; 62 | } 63 | 64 | Object.keys(methods).forEach((m) => { 65 | addHandler(m, methods[m]); 66 | }); 67 | 68 | function sendMessage(method, params, config) { 69 | const id = idGenerator ? idGenerator() : ++callId; 70 | const msg = { 71 | jsonrpc: '2.0', 72 | method, 73 | params, 74 | id 75 | }; 76 | 77 | let timeoutId; 78 | if (config.timeout || timeout) { 79 | timeoutId = setTimeout(() => { 80 | if (pendingCalls[id]) { 81 | const err = new Error('RPC timeout'); 82 | err.code = 504; 83 | pendingCalls[id].reject(err); 84 | delete pendingCalls[id]; 85 | } 86 | }, config.timeout || timeout); 87 | } 88 | 89 | const response = new Promise((resolve, reject) => { 90 | pendingCalls[id] = { resolve, reject, timeoutId }; 91 | }); 92 | 93 | transport.send(msg, config); 94 | 95 | return response; 96 | } 97 | 98 | const methodsProxy = new Proxy({}, { 99 | get: (target, name) => { 100 | return (...args) => { 101 | return sendMessage(name, args, {}); 102 | }; 103 | } 104 | }); 105 | 106 | const configurableMethodsProxy = new Proxy({}, { 107 | get: (target, name) => { 108 | return (...args) => { 109 | let config; 110 | if (args.length) { 111 | const testArg = args.pop(); 112 | if (testArg && typeof testArg === 'object' && !Array.isArray(testArg)) { 113 | config = testArg; 114 | } else { 115 | args.push(testArg); 116 | } 117 | } 118 | return sendMessage(name, args, config || {}); 119 | }; 120 | } 121 | }); 122 | 123 | 124 | const notifiers = new Proxy({}, { 125 | get: (target, name) => { 126 | return (...args) => { 127 | const msg = { 128 | jsonrpc: '2.0', 129 | method: name, 130 | params: args 131 | }; 132 | transport.send(msg); 133 | }; 134 | } 135 | }); 136 | 137 | const configurableNotifiersProxy = new Proxy({}, { 138 | get: (target, name) => { 139 | return (...args) => { 140 | let config; 141 | if (args.length) { 142 | const testArg = args.pop(); 143 | if (testArg && typeof testArg === 'object' && !Array.isArray(testArg)) { 144 | config = testArg; 145 | } else { 146 | args.push(testArg); 147 | } 148 | } 149 | const msg = { 150 | jsonrpc: '2.0', 151 | method: name, 152 | params: args 153 | }; 154 | transport.send(msg, config || {}); 155 | }; 156 | } 157 | }); 158 | 159 | const notifications = new Proxy({}, { 160 | get: (target, name) => { 161 | return (callback) => { 162 | notificationEvents.on(name.substring(2), (...args) => { 163 | return callback.apply(callback, args); 164 | }); 165 | }; 166 | } 167 | }); 168 | 169 | return { 170 | methods: methodsProxy, 171 | methodsExt: configurableMethodsProxy, 172 | addHandler, 173 | notifications, 174 | notifiers, 175 | notifiersExt: configurableNotifiersProxy, 176 | transport, 177 | }; 178 | } 179 | 180 | rawr.transports = transports; 181 | 182 | module.exports = rawr; 183 | -------------------------------------------------------------------------------- /test/mqtt_transport.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const EventEmitter = require('eventemitter3'); 3 | const rawr = require('../'); 4 | 5 | chai.should(); 6 | 7 | function mockTransports() { 8 | const a = new EventEmitter(); 9 | const b = new EventEmitter(); 10 | 11 | a.publish = (topic, msg) => { 12 | b.emit('message', 'aPub', msg); 13 | }; 14 | a.subscribe = () => {}; 15 | 16 | b.publish = (topic, msg) => { 17 | a.emit('message', 'bPub', msg); 18 | }; 19 | b.subscribe = () => {}; 20 | 21 | const transportA = rawr.transports.mqtt({ 22 | connection: a, 23 | pubTopic: 'aPub', 24 | subTopic: 'bPub' 25 | }); 26 | transportA.a = a; 27 | const transportB = rawr.transports.mqtt({ 28 | connection: b, 29 | pubTopic: 'bPub', 30 | subTopic: 'aPub' 31 | }); 32 | transportB.b = b; 33 | 34 | const transportDontSub = rawr.transports.mqtt({ 35 | connection: a, 36 | pubTopic: 'aPub', 37 | subTopic: 'bPub', 38 | subscribe: false, 39 | }); 40 | 41 | const transportBadTopic = rawr.transports.mqtt({ 42 | connection: a, 43 | pubTopic: 'somethingElse', 44 | subTopic: 'somethingElse', 45 | }); 46 | 47 | return { transportA, transportB, transportDontSub, transportBadTopic }; 48 | } 49 | 50 | function helloTest(name) { 51 | return new Promise((resolve, reject) => { 52 | if (name === 'bad') { 53 | const error = new Error('bad name !'); 54 | error.code = 9000; 55 | return reject(error); 56 | } 57 | setTimeout(() => { 58 | return resolve(`hello, ${name}`); 59 | }, 100); 60 | }); 61 | } 62 | 63 | function add(a, b) { 64 | return a + b; 65 | } 66 | 67 | function subtract(a, b) { 68 | return a - b; 69 | } 70 | 71 | describe('mqtt', () => { 72 | it('should make a client', (done) => { 73 | const { transportA, transportB } = mockTransports(); 74 | transportB.b.publish('bPub', 'check bad json'); 75 | const client = rawr({ transport: transportA }); 76 | client.should.be.a('object'); 77 | client.addHandler.should.be.a('function'); 78 | done(); 79 | }); 80 | 81 | it('should make a client with an already subscribed transport', (done) => { 82 | const { transportDontSub, transportB } = mockTransports(); 83 | transportB.b.publish('bPub', 'check bad json'); 84 | const client = rawr({ transport: transportDontSub }); 85 | client.should.be.a('object'); 86 | client.addHandler.should.be.a('function'); 87 | done(); 88 | }); 89 | 90 | it('client should make a successful rpc call to another peer', async () => { 91 | const { transportA, transportB } = mockTransports(); 92 | const clientA = rawr({ transport: transportA, handlers: { add } }); 93 | const clientB = rawr({ transport: transportB, handlers: { subtract } }); 94 | 95 | const resultA = await clientA.methods.subtract(7, 2); 96 | const resultB = await clientB.methods.add(1, 2); 97 | resultA.should.equal(5); 98 | resultB.should.equal(3); 99 | }); 100 | 101 | it('client should handle bad messages on topic', async () => { 102 | const { transportA, transportB } = mockTransports(); 103 | const clientA = rawr({ transport: transportA }); 104 | const clientB = rawr({ transport: transportB, handlers: { subtract } }); 105 | 106 | transportA.a.publish('aPub', `{"something": "bad"}`); 107 | const resultA = await clientA.methods.subtract(7, 2); 108 | resultA.should.equal(5); 109 | }); 110 | 111 | it('client should make an unsuccessful rpc call to a peer', async () => { 112 | const { transportA, transportB } = mockTransports(); 113 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 114 | const clientB = rawr({ transport: transportB }); 115 | 116 | clientA.should.be.an('object'); 117 | try { 118 | await clientB.methods.helloTest('bad'); 119 | } catch (error) { 120 | error.code.should.equal(9000); 121 | } 122 | }); 123 | 124 | it('client handle an rpc under a specified timeout', async () => { 125 | const { transportA, transportB } = mockTransports(); 126 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 127 | const clientB = rawr({ transport: transportB, timeout: 1000 }); 128 | 129 | clientA.should.be.an('object'); 130 | const result = await clientB.methods.helloTest('luis'); 131 | result.should.equal('hello, luis'); 132 | }); 133 | 134 | it('client handle an rpc timeout', async () => { 135 | const { transportA, transportB } = mockTransports(); 136 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 137 | const clientB = rawr({ transport: transportB, timeout: 10 }); 138 | 139 | clientA.should.be.an('object'); 140 | try { 141 | await clientB.methods.helloTest('luis'); 142 | } catch (error) { 143 | error.code.should.equal(504); 144 | } 145 | }); 146 | 147 | it('client handle an rpc timeout becuase topic didnt match', async () => { 148 | const { transportA, transportBadTopic } = mockTransports(); 149 | const clientA = rawr({ transport: transportA, handlers: { helloTest } }); 150 | const clientB = rawr({ transport: transportBadTopic, timeout: 10 }); 151 | 152 | clientA.should.be.an('object'); 153 | try { 154 | await clientB.methods.helloTest('luis'); 155 | } catch (error) { 156 | error.code.should.equal(504); 157 | } 158 | }); 159 | 160 | it('client should be able to send a notification to a server', (done) => { 161 | const { transportA, transportB } = mockTransports(); 162 | const clientA = rawr({ transport: transportA }); 163 | const clientB = rawr({ transport: transportB }); 164 | 165 | clientA.notifications.ondoSomething((someData) => { 166 | someData.should.equal('testing_notification'); 167 | done(); 168 | }); 169 | 170 | clientB.notifiers.doSomething('testing_notification'); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const EventEmitter = require('eventemitter3'); 3 | const b64id = require('b64id'); 4 | const rawr = require('../'); 5 | 6 | chai.should(); 7 | 8 | function mockTransports() { 9 | const a = new EventEmitter(); 10 | const b = new EventEmitter(); 11 | 12 | a.on('message', (msg) => { 13 | a.emit('rpc', msg); 14 | }); 15 | a.send = (msg, config) => { 16 | b.emit('message', msg); 17 | if (config) { 18 | b.emit('config', config); 19 | } 20 | }; 21 | 22 | b.on('message', (msg) => { 23 | b.emit('rpc', msg); 24 | }); 25 | b.send = (msg, config) => { 26 | if (config) { 27 | a.emit('config', config); 28 | } 29 | a.emit('message', msg); 30 | }; 31 | 32 | return { a, b }; 33 | } 34 | 35 | 36 | function helloTest(name) { 37 | return new Promise((resolve, reject) => { 38 | if (name === 'bad') { 39 | const error = new Error('bad name !'); 40 | error.code = 9000; 41 | return reject(error); 42 | } 43 | setTimeout(() => { 44 | return resolve(`hello, ${name}`); 45 | }, 100); 46 | }); 47 | } 48 | 49 | function add(a, b) { 50 | return a + b; 51 | } 52 | 53 | function subtract(a, b) { 54 | return a - b; 55 | } 56 | 57 | function slowFunction() { 58 | return new Promise((resolve) => { 59 | setTimeout(() => { 60 | resolve('slow'); 61 | }, 300); 62 | }); 63 | } 64 | 65 | function hi() { 66 | return 'hi'; 67 | } 68 | 69 | describe('rawr', () => { 70 | it('should make a client', (done) => { 71 | const client = rawr({ transport: mockTransports().a }); 72 | client.should.be.a('object'); 73 | client.addHandler.should.be.a('function'); 74 | done(); 75 | }); 76 | 77 | it('client should make a successful rpc call to another peer', async () => { 78 | const { a, b } = mockTransports(); 79 | const clientA = rawr({ transport: a, handlers: { add } }); 80 | const clientB = rawr({ transport: b, handlers: { subtract } }); 81 | 82 | const resultA = await clientA.methods.subtract(7, 2); 83 | const resultB = await clientB.methods.add(1, 2); 84 | resultA.should.equal(5); 85 | resultB.should.equal(3); 86 | }); 87 | 88 | it('client should make a successful rpc call to another peer with custom id generators', async () => { 89 | const { a, b } = mockTransports(); 90 | const clientA = rawr({ transport: a, handlers: { add }, idGenerator: b64id.generateId }); 91 | const clientB = rawr({ transport: b, handlers: { subtract, idGenerator: b64id.generateId } }); 92 | 93 | const resultA = await clientA.methods.subtract(7, 2); 94 | const resultB = await clientB.methods.add(1, 2); 95 | resultA.should.equal(5); 96 | resultB.should.equal(3); 97 | }); 98 | 99 | it('client should make an unsuccessful rpc call to a peer', async () => { 100 | const { a, b } = mockTransports(); 101 | const clientA = rawr({ transport: a, handlers: { helloTest } }); 102 | const clientB = rawr({ transport: b }); 103 | 104 | clientA.should.be.an('object'); 105 | try { 106 | await clientB.methods.helloTest('bad'); 107 | } catch (error) { 108 | error.code.should.equal(9000); 109 | } 110 | }); 111 | 112 | it('client handle an rpc under a specified timeout', async () => { 113 | const { a, b } = mockTransports(); 114 | const clientA = rawr({ transport: a, handlers: { helloTest } }); 115 | const clientB = rawr({ transport: b, timeout: 1000 }); 116 | 117 | clientA.should.be.an('object'); 118 | const result = await clientB.methods.helloTest('luis'); 119 | result.should.equal('hello, luis'); 120 | }); 121 | 122 | it('client handle an rpc timeout', async () => { 123 | const { a, b } = mockTransports(); 124 | const clientA = rawr({ transport: a, handlers: { helloTest } }); 125 | const clientB = rawr({ transport: b, timeout: 10 }); 126 | 127 | clientA.should.be.an('object'); 128 | try { 129 | await clientB.methods.helloTest('luis'); 130 | } catch (error) { 131 | error.code.should.equal(504); 132 | } 133 | }); 134 | 135 | it('client should be able to send a notification to a server', (done) => { 136 | const { a, b } = mockTransports(); 137 | const clientA = rawr({ transport: a }); 138 | const clientB = rawr({ transport: b }); 139 | 140 | clientA.notifications.ondoSomething((someData) => { 141 | someData.should.equal('testing_notification'); 142 | done(); 143 | }); 144 | 145 | clientB.notifiers.doSomething('testing_notification'); 146 | }); 147 | 148 | it('client should have notifiersExt method', () => { 149 | const { a } = mockTransports(); 150 | const client = rawr({ transport: a }); 151 | client.should.have.property('notifiersExt'); 152 | (typeof client.notifiersExt).should.equal('object'); 153 | }); 154 | 155 | it('client should be able to send a notification with notifiersExt', (done) => { 156 | const { a, b } = mockTransports(); 157 | const clientA = rawr({ transport: a }); 158 | const clientB = rawr({ transport: b }); 159 | 160 | clientA.notifications.ondoSomething((someData) => { 161 | someData.should.equal('testing_notification_ext'); 162 | done(); 163 | }); 164 | 165 | clientB.notifiersExt.doSomething('testing_notification_ext'); 166 | }); 167 | 168 | it('client should pass config to transport when using notifiersExt', (done) => { 169 | const { a, b } = mockTransports(); 170 | const clientA = rawr({ transport: a }); 171 | const clientB = rawr({ transport: b }); 172 | 173 | let receivedConfig = false; 174 | a.on('config', (config) => { 175 | config.should.deep.equal({ postMessageOptions: { transfer: ['test'] } }); 176 | receivedConfig = true; 177 | }); 178 | 179 | clientA.notifications.ondoConfigTest((someData) => { 180 | someData.should.equal('config_test'); 181 | receivedConfig.should.equal(true); 182 | done(); 183 | }); 184 | 185 | clientB.notifiersExt.doConfigTest('config_test', { postMessageOptions: { transfer: ['test'] } }); 186 | }); 187 | 188 | it('client should fail on a configured timeout', async () => { 189 | const { a, b } = mockTransports(); 190 | const clientA = rawr({ transport: a, handlers: { slowFunction, hi } }); 191 | const clientB = rawr({ transport: b, handlers: { slowFunction, add } }); 192 | 193 | const resultA = await clientA.methodsExt.slowFunction({ timeout: 1000 }); 194 | resultA.should.equal('slow'); 195 | const resultA2 = await clientA.methodsExt.add(1, 2, null); 196 | resultA2.should.equal(3); 197 | try { 198 | await clientB.methodsExt.slowFunction({ timeout: 100 }); 199 | 200 | } catch (error) { 201 | error.code.should.equal(504); 202 | } 203 | try { 204 | await clientB.methodsExt.slowFunction('useless param', { timeout: 100 }); 205 | } catch (error) { 206 | error.code.should.equal(504); 207 | } 208 | const resultB2 = await clientB.methodsExt.hi(); 209 | resultB2.should.equal('hi'); 210 | 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { 24 | if (msg.id) { 25 | // handle an RPC request 26 | if (msg.params && methodHandlers[msg.method]) { 27 | methodHandlers[msg.method](msg); 28 | return; 29 | } 30 | // handle an RPC result 31 | const promise = pendingCalls[msg.id]; 32 | if (promise) { 33 | if (promise.timeoutId) { 34 | clearTimeout(promise.timeoutId); 35 | } 36 | delete pendingCalls[msg.id]; 37 | if (msg.error) { 38 | promise.reject(msg.error); 39 | } 40 | return promise.resolve(msg.result); 41 | } 42 | return; 43 | } 44 | // handle a notification 45 | msg.params.unshift(msg.method); 46 | notificationEvents.emit(...msg.params); 47 | }); 48 | 49 | function addHandler(methodName, handler) { 50 | methodHandlers[methodName] = (msg) => { 51 | Promise.resolve() 52 | .then(() => { 53 | return handler.apply(this, msg.params); 54 | }) 55 | .then((result) => { 56 | transport.send({ 57 | id: msg.id, 58 | result 59 | }); 60 | }) 61 | .catch((error) => { 62 | const serializedError = { message: error.message }; 63 | if (error.code) { 64 | serializedError.code = error.code; 65 | } 66 | transport.send({ 67 | id: msg.id, 68 | error: serializedError 69 | }); 70 | }); 71 | }; 72 | } 73 | 74 | Object.keys(methods).forEach((m) => { 75 | addHandler(m, methods[m]); 76 | }); 77 | 78 | function sendMessage(method, params, config) { 79 | const id = idGenerator ? idGenerator() : ++callId; 80 | const msg = { 81 | jsonrpc: '2.0', 82 | method, 83 | params, 84 | id 85 | }; 86 | 87 | let timeoutId; 88 | if (config.timeout || timeout) { 89 | timeoutId = setTimeout(() => { 90 | if (pendingCalls[id]) { 91 | const err = new Error('RPC timeout'); 92 | err.code = 504; 93 | pendingCalls[id].reject(err); 94 | delete pendingCalls[id]; 95 | } 96 | }, config.timeout || timeout); 97 | } 98 | 99 | const response = new Promise((resolve, reject) => { 100 | pendingCalls[id] = { resolve, reject, timeoutId }; 101 | }); 102 | 103 | transport.send(msg, config); 104 | 105 | return response; 106 | } 107 | 108 | const methodsProxy = new Proxy({}, { 109 | get: (target, name) => { 110 | return (...args) => { 111 | return sendMessage(name, args, {}); 112 | }; 113 | } 114 | }); 115 | 116 | const configurableMethodsProxy = new Proxy({}, { 117 | get: (target, name) => { 118 | return (...args) => { 119 | let config; 120 | if (args.length) { 121 | const testArg = args.pop(); 122 | if (testArg && typeof testArg === 'object' && !Array.isArray(testArg)) { 123 | config = testArg; 124 | } else { 125 | args.push(testArg); 126 | } 127 | } 128 | return sendMessage(name, args, config || {}); 129 | }; 130 | } 131 | }); 132 | 133 | 134 | const notifiers = new Proxy({}, { 135 | get: (target, name) => { 136 | return (...args) => { 137 | const msg = { 138 | jsonrpc: '2.0', 139 | method: name, 140 | params: args 141 | }; 142 | transport.send(msg); 143 | }; 144 | } 145 | }); 146 | 147 | const configurableNotifiersProxy = new Proxy({}, { 148 | get: (target, name) => { 149 | return (...args) => { 150 | let config; 151 | if (args.length) { 152 | const testArg = args.pop(); 153 | if (testArg && typeof testArg === 'object' && !Array.isArray(testArg)) { 154 | config = testArg; 155 | } else { 156 | args.push(testArg); 157 | } 158 | } 159 | const msg = { 160 | jsonrpc: '2.0', 161 | method: name, 162 | params: args 163 | }; 164 | transport.send(msg, config || {}); 165 | }; 166 | } 167 | }); 168 | 169 | const notifications = new Proxy({}, { 170 | get: (target, name) => { 171 | return (callback) => { 172 | notificationEvents.on(name.substring(2), (...args) => { 173 | return callback.apply(callback, args); 174 | }); 175 | }; 176 | } 177 | }); 178 | 179 | return { 180 | methods: methodsProxy, 181 | methodsExt: configurableMethodsProxy, 182 | addHandler, 183 | notifications, 184 | notifiers, 185 | notifiersExt: configurableNotifiersProxy, 186 | transport, 187 | }; 188 | } 189 | 190 | rawr.transports = transports; 191 | 192 | module.exports = rawr; 193 | 194 | },{"./transports":4,"eventemitter3":3}],3:[function(require,module,exports){ 195 | 'use strict'; 196 | 197 | var has = Object.prototype.hasOwnProperty 198 | , prefix = '~'; 199 | 200 | /** 201 | * Constructor to create a storage for our `EE` objects. 202 | * An `Events` instance is a plain object whose properties are event names. 203 | * 204 | * @constructor 205 | * @private 206 | */ 207 | function Events() {} 208 | 209 | // 210 | // We try to not inherit from `Object.prototype`. In some engines creating an 211 | // instance in this way is faster than calling `Object.create(null)` directly. 212 | // If `Object.create(null)` is not supported we prefix the event names with a 213 | // character to make sure that the built-in object properties are not 214 | // overridden or used as an attack vector. 215 | // 216 | if (Object.create) { 217 | Events.prototype = Object.create(null); 218 | 219 | // 220 | // This hack is needed because the `__proto__` property is still inherited in 221 | // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. 222 | // 223 | if (!new Events().__proto__) prefix = false; 224 | } 225 | 226 | /** 227 | * Representation of a single event listener. 228 | * 229 | * @param {Function} fn The listener function. 230 | * @param {*} context The context to invoke the listener with. 231 | * @param {Boolean} [once=false] Specify if the listener is a one-time listener. 232 | * @constructor 233 | * @private 234 | */ 235 | function EE(fn, context, once) { 236 | this.fn = fn; 237 | this.context = context; 238 | this.once = once || false; 239 | } 240 | 241 | /** 242 | * Add a listener for a given event. 243 | * 244 | * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. 245 | * @param {(String|Symbol)} event The event name. 246 | * @param {Function} fn The listener function. 247 | * @param {*} context The context to invoke the listener with. 248 | * @param {Boolean} once Specify if the listener is a one-time listener. 249 | * @returns {EventEmitter} 250 | * @private 251 | */ 252 | function addListener(emitter, event, fn, context, once) { 253 | if (typeof fn !== 'function') { 254 | throw new TypeError('The listener must be a function'); 255 | } 256 | 257 | var listener = new EE(fn, context || emitter, once) 258 | , evt = prefix ? prefix + event : event; 259 | 260 | if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; 261 | else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); 262 | else emitter._events[evt] = [emitter._events[evt], listener]; 263 | 264 | return emitter; 265 | } 266 | 267 | /** 268 | * Clear event by name. 269 | * 270 | * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. 271 | * @param {(String|Symbol)} evt The Event name. 272 | * @private 273 | */ 274 | function clearEvent(emitter, evt) { 275 | if (--emitter._eventsCount === 0) emitter._events = new Events(); 276 | else delete emitter._events[evt]; 277 | } 278 | 279 | /** 280 | * Minimal `EventEmitter` interface that is molded against the Node.js 281 | * `EventEmitter` interface. 282 | * 283 | * @constructor 284 | * @public 285 | */ 286 | function EventEmitter() { 287 | this._events = new Events(); 288 | this._eventsCount = 0; 289 | } 290 | 291 | /** 292 | * Return an array listing the events for which the emitter has registered 293 | * listeners. 294 | * 295 | * @returns {Array} 296 | * @public 297 | */ 298 | EventEmitter.prototype.eventNames = function eventNames() { 299 | var names = [] 300 | , events 301 | , name; 302 | 303 | if (this._eventsCount === 0) return names; 304 | 305 | for (name in (events = this._events)) { 306 | if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); 307 | } 308 | 309 | if (Object.getOwnPropertySymbols) { 310 | return names.concat(Object.getOwnPropertySymbols(events)); 311 | } 312 | 313 | return names; 314 | }; 315 | 316 | /** 317 | * Return the listeners registered for a given event. 318 | * 319 | * @param {(String|Symbol)} event The event name. 320 | * @returns {Array} The registered listeners. 321 | * @public 322 | */ 323 | EventEmitter.prototype.listeners = function listeners(event) { 324 | var evt = prefix ? prefix + event : event 325 | , handlers = this._events[evt]; 326 | 327 | if (!handlers) return []; 328 | if (handlers.fn) return [handlers.fn]; 329 | 330 | for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { 331 | ee[i] = handlers[i].fn; 332 | } 333 | 334 | return ee; 335 | }; 336 | 337 | /** 338 | * Return the number of listeners listening to a given event. 339 | * 340 | * @param {(String|Symbol)} event The event name. 341 | * @returns {Number} The number of listeners. 342 | * @public 343 | */ 344 | EventEmitter.prototype.listenerCount = function listenerCount(event) { 345 | var evt = prefix ? prefix + event : event 346 | , listeners = this._events[evt]; 347 | 348 | if (!listeners) return 0; 349 | if (listeners.fn) return 1; 350 | return listeners.length; 351 | }; 352 | 353 | /** 354 | * Calls each of the listeners registered for a given event. 355 | * 356 | * @param {(String|Symbol)} event The event name. 357 | * @returns {Boolean} `true` if the event had listeners, else `false`. 358 | * @public 359 | */ 360 | EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { 361 | var evt = prefix ? prefix + event : event; 362 | 363 | if (!this._events[evt]) return false; 364 | 365 | var listeners = this._events[evt] 366 | , len = arguments.length 367 | , args 368 | , i; 369 | 370 | if (listeners.fn) { 371 | if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); 372 | 373 | switch (len) { 374 | case 1: return listeners.fn.call(listeners.context), true; 375 | case 2: return listeners.fn.call(listeners.context, a1), true; 376 | case 3: return listeners.fn.call(listeners.context, a1, a2), true; 377 | case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; 378 | case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; 379 | case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; 380 | } 381 | 382 | for (i = 1, args = new Array(len -1); i < len; i++) { 383 | args[i - 1] = arguments[i]; 384 | } 385 | 386 | listeners.fn.apply(listeners.context, args); 387 | } else { 388 | var length = listeners.length 389 | , j; 390 | 391 | for (i = 0; i < length; i++) { 392 | if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); 393 | 394 | switch (len) { 395 | case 1: listeners[i].fn.call(listeners[i].context); break; 396 | case 2: listeners[i].fn.call(listeners[i].context, a1); break; 397 | case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; 398 | case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; 399 | default: 400 | if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { 401 | args[j - 1] = arguments[j]; 402 | } 403 | 404 | listeners[i].fn.apply(listeners[i].context, args); 405 | } 406 | } 407 | } 408 | 409 | return true; 410 | }; 411 | 412 | /** 413 | * Add a listener for a given event. 414 | * 415 | * @param {(String|Symbol)} event The event name. 416 | * @param {Function} fn The listener function. 417 | * @param {*} [context=this] The context to invoke the listener with. 418 | * @returns {EventEmitter} `this`. 419 | * @public 420 | */ 421 | EventEmitter.prototype.on = function on(event, fn, context) { 422 | return addListener(this, event, fn, context, false); 423 | }; 424 | 425 | /** 426 | * Add a one-time listener for a given event. 427 | * 428 | * @param {(String|Symbol)} event The event name. 429 | * @param {Function} fn The listener function. 430 | * @param {*} [context=this] The context to invoke the listener with. 431 | * @returns {EventEmitter} `this`. 432 | * @public 433 | */ 434 | EventEmitter.prototype.once = function once(event, fn, context) { 435 | return addListener(this, event, fn, context, true); 436 | }; 437 | 438 | /** 439 | * Remove the listeners of a given event. 440 | * 441 | * @param {(String|Symbol)} event The event name. 442 | * @param {Function} fn Only remove the listeners that match this function. 443 | * @param {*} context Only remove the listeners that have this context. 444 | * @param {Boolean} once Only remove one-time listeners. 445 | * @returns {EventEmitter} `this`. 446 | * @public 447 | */ 448 | EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { 449 | var evt = prefix ? prefix + event : event; 450 | 451 | if (!this._events[evt]) return this; 452 | if (!fn) { 453 | clearEvent(this, evt); 454 | return this; 455 | } 456 | 457 | var listeners = this._events[evt]; 458 | 459 | if (listeners.fn) { 460 | if ( 461 | listeners.fn === fn && 462 | (!once || listeners.once) && 463 | (!context || listeners.context === context) 464 | ) { 465 | clearEvent(this, evt); 466 | } 467 | } else { 468 | for (var i = 0, events = [], length = listeners.length; i < length; i++) { 469 | if ( 470 | listeners[i].fn !== fn || 471 | (once && !listeners[i].once) || 472 | (context && listeners[i].context !== context) 473 | ) { 474 | events.push(listeners[i]); 475 | } 476 | } 477 | 478 | // 479 | // Reset the array, or remove it completely if we have no more listeners. 480 | // 481 | if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; 482 | else clearEvent(this, evt); 483 | } 484 | 485 | return this; 486 | }; 487 | 488 | /** 489 | * Remove all listeners, or those of the specified event. 490 | * 491 | * @param {(String|Symbol)} [event] The event name. 492 | * @returns {EventEmitter} `this`. 493 | * @public 494 | */ 495 | EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { 496 | var evt; 497 | 498 | if (event) { 499 | evt = prefix ? prefix + event : event; 500 | if (this._events[evt]) clearEvent(this, evt); 501 | } else { 502 | this._events = new Events(); 503 | this._eventsCount = 0; 504 | } 505 | 506 | return this; 507 | }; 508 | 509 | // 510 | // Alias methods names because people roll like that. 511 | // 512 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 513 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 514 | 515 | // 516 | // Expose the prefix. 517 | // 518 | EventEmitter.prefixed = prefix; 519 | 520 | // 521 | // Allow `EventEmitter` to be imported as module namespace. 522 | // 523 | EventEmitter.EventEmitter = EventEmitter; 524 | 525 | // 526 | // Expose the module. 527 | // 528 | if ('undefined' !== typeof module) { 529 | module.exports = EventEmitter; 530 | } 531 | 532 | },{}],4:[function(require,module,exports){ 533 | const mqtt = require('./mqtt'); 534 | const socketio = require('./socketio'); 535 | const websocket = require('./websocket'); 536 | const worker = require('./worker'); 537 | 538 | module.exports = { 539 | mqtt, 540 | socketio, 541 | websocket, 542 | worker 543 | }; 544 | },{"./mqtt":5,"./socketio":6,"./websocket":7,"./worker":8}],5:[function(require,module,exports){ 545 | const EventEmitter = require('eventemitter3'); 546 | 547 | function transport({ connection, subTopic, pubTopic, subscribe = true }) { 548 | const emitter = new EventEmitter(); 549 | if (subscribe) { 550 | connection.subscribe(subTopic); 551 | } 552 | connection.on('message', (topic, message) => { 553 | if (topic === subTopic) { 554 | try { 555 | const msg = JSON.parse(message.toString()); 556 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 557 | emitter.emit('rpc', msg); 558 | } 559 | } catch (err) { 560 | console.error(err); 561 | } 562 | } 563 | }); 564 | emitter.send = (msg) => { 565 | connection.publish(pubTopic, JSON.stringify(msg)); 566 | }; 567 | return emitter; 568 | } 569 | 570 | module.exports = transport; 571 | 572 | },{"eventemitter3":3}],6:[function(require,module,exports){ 573 | const EventEmitter = require('eventemitter3'); 574 | 575 | function transport({ connection, subTopic, pubTopic }) { 576 | const emitter = new EventEmitter(); 577 | connection.on(subTopic, (msg) => { 578 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 579 | emitter.emit('rpc', msg); 580 | } 581 | }); 582 | emitter.send = (msg) => { 583 | connection.emit(pubTopic, msg); 584 | }; 585 | return emitter; 586 | } 587 | 588 | module.exports = transport; 589 | 590 | },{"eventemitter3":3}],7:[function(require,module,exports){ 591 | const EventEmitter = require('eventemitter3'); 592 | 593 | function transport(socket, allowBinary = false) { 594 | const emitter = new EventEmitter(); 595 | socket.addEventListener('message', async (evt) => { 596 | let { data } = evt; 597 | if (allowBinary && data instanceof Blob) { 598 | data = await (new Response(data)).text().catch(() => null); 599 | } 600 | if (typeof evt.data === 'string') { 601 | try { 602 | const msg = JSON.parse(evt.data); 603 | if (msg.method || (msg.id && ('result' in msg || 'error' in msg))) { 604 | emitter.emit('rpc', msg); 605 | } 606 | } catch (err) { 607 | // wasn't a JSON message 608 | } 609 | } 610 | }); 611 | emitter.send = (msg) => { 612 | socket.send(JSON.stringify(msg)); 613 | }; 614 | return emitter; 615 | } 616 | 617 | module.exports = transport; 618 | 619 | },{"eventemitter3":3}],8:[function(require,module,exports){ 620 | const EventEmitter = require('eventemitter3'); 621 | 622 | function dom(webWorker) { 623 | const emitter = new EventEmitter(); 624 | webWorker.addEventListener('message', (msg) => { 625 | const { data } = msg; 626 | if (data && (data.method || (data.id && ('result' in data || 'error' in data)))) { 627 | emitter.emit('rpc', data); 628 | } 629 | }); 630 | emitter.send = (msg, config) => { 631 | webWorker.postMessage(msg, config ? config.postMessageOptions : undefined); 632 | }; 633 | return emitter; 634 | } 635 | 636 | function worker() { 637 | const emitter = new EventEmitter(); 638 | self.onmessage = (msg) => { 639 | const { data } = msg; 640 | if (data && (data.method || (data.id && ('result' in data || 'error' in data)))) { 641 | emitter.emit('rpc', data); 642 | } 643 | }; 644 | emitter.send = (msg, config) => { 645 | self.postMessage(msg, config ? config.postMessageOptions : undefined); 646 | }; 647 | return emitter; 648 | } 649 | 650 | function transport(webWorker) { 651 | if (webWorker) { 652 | return dom(webWorker); 653 | } 654 | return worker(); 655 | } 656 | 657 | // backwards compat 658 | transport.dom = dom; 659 | transport.worker = worker; 660 | 661 | module.exports = transport; 662 | 663 | },{"eventemitter3":3}]},{},[1]); 664 | --------------------------------------------------------------------------------