├── .gitignore ├── LICENSE ├── README.md ├── browser.js ├── demos ├── nodejs │ └── server.js └── web │ └── index.html ├── index.js ├── lib └── wscb.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aid Vllasaliu / Antiphase AB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # websockets-callback 2 | WebSocket messages with callbacks. 3 | There's a C++ port of this library called [WebSockets-Callback.CPP](https://github.com/aidv/WebSockets-Callback.CPP) 4 | 5 | ## NEW IN 0.4.8 6 | 7 | Now supports Electron. 8 | 9 | ### Projects using websockets-callback 10 | - [I2Catalyst](https://github.com/aidv/i2catalyst) - A browser based I2C packet analyzer using NodeJS 11 | - [jobmatch-er](https://github.com/jobmatch-er/jobmatch.er-ws) - We don't know what this project does, but thanks for using WSCB 12 | 13 | 14 | ### Introduction 15 | 16 | WSCB's goal is to be the easiest solution to communicate between devices using Websockets. 17 | 18 | It contains two main functions: `on()` and `send()`. 19 | 20 | It has two types of messages: `Expectations` and `Unexpected`. 21 | 22 | Expectations are messages that trigger an `on()` binding on the receiving end of the pipe. 23 | 24 | Unexpected messages will trigger a commonly shared function. 25 | 26 | ### Expectations 27 | 28 | An expectation callback has two arguments: `msg` and `respondWith()`. 29 | 30 | The `msg` object contains the data that the sender has sent, and `respondWith(obj)` is a function that takes an object as its argument. 31 | 32 | The function `respondWith()` is used to reply to the sender, usually used as an ACK signal. 33 | 34 | Example: 35 | 36 | ```js 37 | wscb.on('human', function(msg, respondWith){ 38 | respondWith({human: 'homosapien'}); 39 | }); 40 | ``` 41 | 42 | 43 | ### Coding fashion 44 | Firstly, it's important to understand that the file ```wscb.js``` inside the folder ```./lib``` is cross compatible with both NodeJS and the browser (in my case Chrome). 45 | 46 | To create a trigger you call the ```on(object, onHandle)``` function and pass an object (as the message) and specify a handler function. 47 | The handler function is called when the trigger is triggered. 48 | The handler function has two parameters: 49 | - The message 50 | - A response function called ```respondWith(object)``` that you call when you want to respond to the message. 51 | NOTE: 52 | If the response object contains the key ```progress```, the expectation on the other end of the pipe will not be removed 53 | and allows for several responses to be sent until you either (A) set the ```progress``` value to ```100``` or respond 54 | without the ```progress``` key. 55 | 56 | 57 | To send an expectation (*1*) you call the ```send(object, onResponse, onProgress, connection)``` and feed an object (as the message), the response handler, the progress handler and the connection to that should carry the message. 58 | 59 | It's also important to note that the connection parameter is only used in NodeJS. 60 | Why is that? Because in NodeJS you're most likely to run a server (although you can create a client too) and so when you want to send an expectation or a responseless message to a client, you also need to define who you're sending it to. Thus the connection parameter has to be defined upon calling the ```send(object, onResponse, onProgress, connection)``` function. 61 | 62 | The progress handler is only called when the key ```progress``` exists in the response message. 63 | The ```progress``` value has a range of 0 to 100 where when at 100 (or non-existant) the expectation is deleted 64 | 65 | (*1*) An "expectation" is a message that expects a response. If no response is received, the expectation will wait forever. 66 | 67 | 68 | #### Using in an Electron app 69 | 70 | In your `main.js` add the following: 71 | ```js 72 | const WebSockets_Callback = require('wscb'); 73 | var wscb = new WebSockets_Callback({asElectron: true}) 74 | 75 | //follow the rest of the instructions below 76 | ``` 77 | 78 | In your `index.html` add the following in the body: 79 | ```js 80 | 86 | ``` 87 | 88 | 89 | 90 | 91 | #### Creating a trigger (cross compatible) 92 | A trigger is triggered when a specified message is received. 93 | Once the trigger shoots and you've handled the message, you can respond to the message by calling ```respondWith()``` 94 | 95 | ```js 96 | wscb.on('human', 97 | function(msg, respondWith){ 98 | respondWith({human: 'homosapien'}); 99 | } 100 | ); 101 | ``` 102 | 103 | #### Sending an expectation 104 | 105 | ```js 106 | wscb.send( 107 | {key: 'value', greeting: 'hello world!'}, 108 | function(response){ 109 | 110 | }, 111 | function(response){ 112 | console.log(response.progress + '% done'); 113 | }, 114 | conn //set to undefined or ignore if sending from the browser 115 | ); 116 | ``` 117 | 118 | ### To install from npm: 119 | ``` 120 | npm i wscb 121 | ``` 122 | 123 | ### Options: 124 | ```js 125 | var options = { 126 | verbose: false, //will log some messages to the console 127 | asClient: false, //will setup WSCB as a client. Browser incompatible. 128 | asElectron: false, //will use Electron IPC instead of Websockets 129 | 130 | address: '127.0.0.1', 131 | port: 8081, 132 | onOpen: undefined, 133 | onError: undefined, 134 | onListening: undefined, 135 | onUnexpectedMessage: undefined 136 | } 137 | ``` 138 | 139 | ### NodeJS sample code: 140 | ```js 141 | const WebSockets_Callback = require('wscb'); 142 | 143 | 144 | var options = {} 145 | 146 | var wscb = new WebSockets_Callback(options); 147 | 148 | wscb.on('hello from client :)', function(msg, respondWith){ 149 | console.log('Client said:') 150 | console.log(msg) 151 | respondWith({msg: 'hi from server :D'}); 152 | }) 153 | 154 | wscb.on('waitFor', function(msg, respondWith){ 155 | setTimeout(() => { 156 | respondWith({msg: 'Delayed for ' + msg.delay + ' ms'}); 157 | }, msg.delay); 158 | 159 | }) 160 | 161 | wscb.on('progress', function(msg, respondWith){ 162 | var progress = -1; 163 | var progressTimer = setInterval(() => { 164 | progress++; 165 | if (progress >= 100){ 166 | progress = 100; 167 | clearInterval(progressTimer); 168 | } 169 | respondWith({progress: progress}); 170 | }, 10); 171 | }) 172 | 173 | wscb.options.onUnexpectedMessage = function(conn, msg){ 174 | console.log('Client sent a responseless message: ' + msg) 175 | } 176 | 177 | 178 | //wait for client to connect 179 | wscb.options.onOpen = function(conn){ 180 | console.log('Client connected') 181 | //Send some tests to client and wait for responses 182 | console.log('Sending waitFor test (3000 ms)...') 183 | wscb.send({cmd: 'waitFor', delay: 3000}, 184 | function(response){ 185 | console.log(response.msg); 186 | 187 | setTimeout(progressTest, 2000); 188 | }, 189 | undefined, //not expecting any progress 190 | conn 191 | ) 192 | 193 | function progressTest(){ 194 | wscb.send({cmd: 'progress'}, 195 | function(response){ 196 | console.log('Progress test completed!'); 197 | }, 198 | function(response){ 199 | console.log(response.progress + '% done'); 200 | }, 201 | conn 202 | ) 203 | } 204 | } 205 | ``` 206 | 207 | ### Browser sample code: 208 | ```html 209 | 210 | 211 | 212 | Websockets Callback Demo 213 | 214 | 215 | 216 | milliseconds 217 |
218 | 219 |
220 |
221 | 222 |
223 | 224 |
225 |
226 | 227 |
228 | Server Response: 229 |
230 |
231 | 232 | 299 | 300 | 301 | ``` 302 | 303 | ### Additional Information 304 | 305 | You can access the underlying WebSocket onConnect/onMessage/onError events by passing functions during construction: 306 | ```js 307 | const WebSockets_Callback = require('wscb'); 308 | var wscb = new WebSockets_Callback({ 309 | onOpen: function(conn){ 310 | 311 | }, 312 | 313 | onError: function(conn, error){ 314 | 315 | }, 316 | 317 | onListening: function(){ 318 | 319 | } 320 | }); 321 | ``` 322 | 323 | Star this repository on github, please. Thank you. 324 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | throw new Error( 5 | 'ws does not work in the browser. Browser clients must use the native ' + 6 | 'WebSockets-Callback object' 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /demos/nodejs/server.js: -------------------------------------------------------------------------------- 1 | const WebSockets_Callback = require('wscb'); 2 | var wscb = new WebSockets_Callback(); 3 | 4 | wscb.on('hello from client :)', function(msg, respondWith){ 5 | console.log('Client said:') 6 | console.log(msg) 7 | respondWith({msg: 'hi from server :D'}); 8 | }) 9 | 10 | wscb.on('waitFor', function(msg, respondWith){ 11 | setTimeout(() => { 12 | respondWith({msg: 'Delayed for ' + msg.delay + ' ms'}); 13 | }, msg.delay); 14 | 15 | }) 16 | 17 | wscb.on('progress', function(msg, respondWith){ 18 | var progress = -1; 19 | var progressTimer = setInterval(() => { 20 | progress++; 21 | if (progress >= 100){ 22 | progress = 100; 23 | clearInterval(progressTimer); 24 | } 25 | respondWith({progress: progress}); 26 | }, 10); 27 | }) 28 | 29 | wscb.options.onUnexpectedMessage = function(conn, msg){ 30 | console.log('Client sent a responseless message: ' + msg) 31 | } 32 | 33 | 34 | //wait for client to connect 35 | wscb.options.onOpen = function(conn){ 36 | console.log('Client connected') 37 | //Send some tests to client and wait for responses 38 | console.log('Sending waitFor test (3000 ms)...') 39 | wscb.send({cmd: 'waitFor', delay: 3000}, 40 | function(response){ 41 | console.log(response.msg); 42 | 43 | setTimeout(progressTest, 2000); 44 | }, 45 | undefined, //not expecting any progress 46 | conn 47 | ) 48 | 49 | function progressTest(){ 50 | wscb.send({cmd: 'progress'}, 51 | function(response){ 52 | console.log('Progress test completed!'); 53 | }, 54 | function(response){ 55 | console.log(response.progress + '% done'); 56 | }, 57 | conn 58 | ) 59 | } 60 | } -------------------------------------------------------------------------------- /demos/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Websockets Callback Demo 5 | 6 | 7 | 8 | milliseconds 9 |
10 | 11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | Server Response: 21 |
22 |
23 | 24 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const WebSockets_Callback = require('./lib/wscb'); 4 | 5 | module.exports = WebSockets_Callback; -------------------------------------------------------------------------------- /lib/wscb.js: -------------------------------------------------------------------------------- 1 | var ipc, WebSocket; 2 | 3 | class WebSockets_Callback{ 4 | constructor(options){ 5 | 6 | this.triggers = {} 7 | this.expectations = {} 8 | this.clients = [] 9 | 10 | this.options = { 11 | verbose: false, //will log some messages to the console 12 | asClient: false, //will setup WSCB as a client. Browser incompatible. 13 | address: '127.0.0.1', 14 | port: 8081, 15 | onOpen: undefined, 16 | onError: undefined, 17 | onListening: undefined, 18 | onUnexpectedMessage: undefined 19 | } 20 | this.options = {...this.options, ...options} 21 | 22 | if ( ((typeof process !== 'undefined') && (process.release.name === 'node')) && (this.options.asClient == undefined || this.options.asClient == false) ) 23 | this.setupAsServer() 24 | else 25 | this.setupAsClient() 26 | 27 | 28 | if (this.options.asElectron){ 29 | 30 | 31 | if (this.options.asClient) ipc = require('electron').ipcRenderer 32 | else ipc = require('electron').ipcMain 33 | 34 | if (!this.options.asClient){ 35 | const { webContents } = require('electron') 36 | this.sendData = msg => { 37 | var wcs = webContents.getAllWebContents() 38 | for (var i in wcs){ 39 | var wc = wcs[i] 40 | wc.send('wscb', msg) 41 | } 42 | } 43 | 44 | ipc.on('wscb', (event, arg)=>{ 45 | this.handleMessage(this, {...{senderID: event.sender.id}, ...arg}, {send: (msg)=>{ 46 | event.reply('wscb', msg) 47 | }}) 48 | }) 49 | 50 | 51 | return 52 | } 53 | 54 | 55 | this.sendData = msg => { 56 | ipc.send('wscb', msg) 57 | } 58 | ipc.on('wscb', (event, arg)=>{ 59 | this.handleMessage(this, arg, {send: (msg)=>{ 60 | ipc.send('wscb', msg) 61 | }}) 62 | }) 63 | } else { 64 | WebSocket = require('ws'); 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | } 74 | 75 | log(str){ 76 | if (this.options.verbose) console.log('[ WebSockets-Callback ] ' + str); 77 | } 78 | 79 | setupAsServer(){ 80 | this.ws_types = {expector: '#s', responder: '#c'}; 81 | 82 | var t = this; 83 | 84 | if (this.options.asElectron) return 85 | 86 | this.ws = new WebSocket.Server({ port: this.options.port }); 87 | 88 | this.log('Starting server @ port ' + t.options.port + '...') 89 | this.ws.on('connection', function(conn) { 90 | 91 | var cuid = 'c:' + Date.now(); 92 | conn.id = cuid; 93 | t.clients[cuid] = conn; 94 | 95 | if (t.options.onOpen != null) t.options.onOpen(conn) 96 | conn.on('message', function(message){ 97 | var json = JSON.parse(message); 98 | t.handleMessage(t, json) 99 | }).on('close', function(event){ 100 | if (t.options.onClose != undefined) t.options.onClose(event); 101 | console.log('Client disconnected!'); 102 | delete t.clients[this.id]; 103 | }); 104 | 105 | }).on('error', function(conn, error){ 106 | if (t.options.onError != null) t.options.onError(conn, error) 107 | console.log('[WS ERROR]') 108 | console.log(error) 109 | }).on('listening', function(){ 110 | if (t.options.onListening != null) t.options.onListening() 111 | t.log('Server listening @ port ' + t.options.port); 112 | }); 113 | 114 | 115 | } 116 | 117 | setupAsClient(){ 118 | this.ws_types = {expector: '#c', responder: '#s'}; 119 | 120 | var t = this; 121 | 122 | if (this.options.asElectron) return 123 | 124 | this.log('Connecting to server ' + this.options.address + ' @ ' + this.options.port + '...') 125 | this.ws = new WebSocket('ws://' + this.options.address + ':' + this.options.port); 126 | 127 | this.ws.onopen = function (event) { 128 | if (t.options.onOpen != undefined) t.options.onOpen(); 129 | t.log('Connected to server ' + t.options.address + ' @ ' + t.options.port) 130 | }; 131 | 132 | this.ws.onmessage = function (event) { 133 | var json = JSON.parse(event.data); 134 | t.handleMessage(t, json) 135 | } 136 | 137 | this.ws.onerror = function (event) { 138 | if (t.options.onError != null) t.options.onError(event.error) 139 | console.log('[WS ERROR]') 140 | console.log(event.error) 141 | } 142 | 143 | this.ws.onclose = function(event){ 144 | if (t.options.onClose != undefined) t.options.onClose(event); 145 | }; 146 | } 147 | 148 | 149 | handleMessage(t, json, conn){ 150 | if (json.puid != undefined){ 151 | var sender = json.puid.substr(0,2); 152 | if (sender == this.ws_types.expector) 153 | t.handleResponse(t, json, conn) 154 | else if (sender == this.ws_types.responder) 155 | t.handleExpectation(t, json, conn) 156 | } else { 157 | if (t.options.onUnexpectedMessage != undefined) t.options.onUnexpectedMessage(json) 158 | } 159 | } 160 | 161 | handleResponse(t, json, conn = undefined){ 162 | //server responded to a message that we expected to have a response 163 | if (t.expectations[json.puid] != undefined){ 164 | if (json.progress == undefined || json.progress == 100){ 165 | t.expectations[json.puid].onResponse(json); 166 | delete t.expectations[json.puid]; 167 | } else { 168 | t.expectations[json.puid].onProgress(json); 169 | } 170 | } 171 | } 172 | 173 | handleExpectation(t, json, conn = undefined){ 174 | t.triggers[json.cmd].doHandle(json, function(response){ 175 | if (response.puid == undefined) response.puid = json.puid; 176 | t.send(response, undefined, undefined, conn); 177 | }) 178 | } 179 | 180 | 181 | send(message, onResponse = undefined, onProgress = undefined, conn = undefined){ 182 | if (message == undefined) return; 183 | 184 | if (onResponse != undefined){ 185 | var puid = this.ws_types.expector + Math.random(); //Date.now(); 186 | this.expectations[puid] = {onResponse: onResponse, onProgress: onProgress}; 187 | message.puid = puid; 188 | } 189 | 190 | if (conn != undefined){ 191 | if (this.options.asElectron) conn.send(message) 192 | else conn.send(JSON.stringify(message)); 193 | } else { 194 | if (this.ws_types.expector == '#s'){ 195 | if (this.options.asElectron){ 196 | this.sendData(message) 197 | } else{ 198 | for (var i = 0; i < Object.keys(this.clients).length; i++){ 199 | var key = Object.keys(this.clients)[i]; 200 | this.clients[key].send(JSON.stringify(message)); 201 | } 202 | } 203 | } else { 204 | if (this.options.asElectron) this.sendData(message) 205 | else this.ws.send(JSON.stringify(message)); 206 | } 207 | } 208 | } 209 | 210 | simple(msg, onResponse = undefined, t = undefined){ 211 | var conn = conn 212 | if (conn != undefined || this.options.asElectron){ 213 | conn.send({cmd: msg}); 214 | } else { 215 | if (this.ws_types.expector == '#s'){ 216 | for (var i = 0; i < Object.keys(this.clients).length; i++){ 217 | var key = Object.keys(this.clients)[i]; 218 | this.clients[key].send({cmd: msg}); 219 | } 220 | } else { 221 | this.ws.send({cmd: msg}); 222 | } 223 | } 224 | } 225 | 226 | on(command, doHandle){ 227 | this.triggers[command] = {doHandle: doHandle} 228 | } 229 | } 230 | 231 | 232 | if ( (typeof process !== 'undefined') && (process.release.name === 'node') ){ 233 | module.exports = WebSockets_Callback; 234 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wscb", 3 | "version": "0.4.91", 4 | "description": "WebSockets with message callbacks", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/aidv/websockets-callback.git" 12 | }, 13 | "keywords": [ 14 | "websocket", 15 | "callback", 16 | "network" 17 | ], 18 | "author": "Aid Vllasaliu", 19 | "browser": "browser.js", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/aidv/websockets-callback/issues" 23 | }, 24 | "homepage": "https://github.com/aidv/websockets-callback#readme", 25 | "dependencies": { 26 | "ws": "6.1.4" 27 | } 28 | } 29 | --------------------------------------------------------------------------------