├── .gitignore ├── Gruntfile.js ├── README.md ├── WebSocket.js ├── WebSocketManager.js ├── bower.json ├── demo ├── demo.js ├── index.html └── server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.*~ 2 | node_modules/ 3 | bower_components/ 4 | .idea/ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | 4 | grunt.initConfig ({ 5 | uglify: { 6 | dist: { 7 | files: { 8 | 'WebSocket.min.js': 'WebSocket.js' , 9 | 'WebSocketManager.min.js': 'WebSocketManager.js' 10 | } 11 | } 12 | } , 13 | jshint: { 14 | dist: { 15 | options: { 16 | globals: { 17 | Ext: true 18 | } , 19 | eqeqeq: true , 20 | undef: true , 21 | eqnull: true , 22 | browser: true , 23 | smarttabs: true , 24 | loopfunc: true 25 | } , 26 | src: ['WebSocket.js', 'WebSocketManager.js'] 27 | } 28 | } 29 | }); 30 | 31 | grunt.registerTask ('check', ['jshint']); 32 | grunt.registerTask ('minify', ['uglify']); 33 | grunt.registerTask ('build', ['check', 'minify']); 34 | }; 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExtJS-WebSocket 2 | 3 | # This library is no longer mainteined. 4 | 5 | ExtJS-WebSocket is an extension to handle and use the HTML5 WebSocket with ExtJS. 6 | 7 | It has two classes: `Ext.ux.WebSocket` and `Ext.ux.WebSocketManager`
8 | The first one is a wrapper for standard HTML5 WebSocket and it provides a lot of interesting and easy-to-use features. 9 | The second one is a singleton to register different Ext.ux.WebSocket and it provides functions to work with every registered websocket at the same time. 10 | 11 | ## ExtJS 5 12 | The new version of ExtJS 5 has requested to make a new major version of `ExtJS-WebSocket`. 13 | Now, this new major version **v1.0.0** is located on the master branch. 14 | 15 | ## ExtJS 4 & Sencha Touch 2 16 | It's possible to work either with ExtJS 4 and Sencha Touch 2 with previous version **v0.0.5** 17 | 18 | ## Install via Bower 19 | First of all, install [**Bower**](http://bower.io/). 20 | 21 | Then install `Ext.ux.WebSocket` (version v1.x.x for ExtJS 5): 22 | 23 | ```bash 24 | $ bower install ext.ux.websocket 25 | ``` 26 | 27 | Or install `Ext.ux.WebSocket` (version v0.0.5 for ExtJS 4 & Sencha Touch 2): 28 | 29 | ```bash 30 | $ bower install ext.ux.websocket#0.0.5 31 | ``` 32 | 33 | Now, you got the extension at the following path: *YOUR_PROJECT_PATH/bower_components/ext.ux.websocket/* 34 | 35 | It contains **WebSocket.js** and **WebSocketManager.js** files. 36 | 37 | Let's setup the **Ext.Loader** to require the right file: 38 | 39 | ```javascript 40 | Ext.Loader.setConfig ({ 41 | enabled: true , 42 | paths: { 43 | 'Ext.ux.WebSocket': 'bower_components/ext.ux.websocket/WebSocket.js' , 44 | 'Ext.ux.WebSocketManager': 'bower_components/ext.ux.websocket/WebSocketManager.js' 45 | } 46 | }); 47 | 48 | Ext.require (['Ext.ux.WebSocket', 'Ext.ux.WebSocketManager']); 49 | ``` 50 | 51 | ## Usage 52 | Load `Ext.ux.WebSocket` and `Ext.ux.WebSocketManager` via `Ext.require`: 53 | 54 | ```javascript 55 | Ext.Loader.setConfig ({ 56 | enabled: true 57 | }); 58 | 59 | Ext.require (['Ext.ux.WebSocket', 'Ext.ux.WebSocketManager']); 60 | ``` 61 | 62 | Now, you are ready to use them in your code as follows: 63 | 64 | ```javascript 65 | // Creating a new instance of Ext.ux.WebSocket 66 | var ws = Ext.create ('Ext.ux.WebSocket', { 67 | url: 'your_url:your_port' , 68 | protocol: 'your_protocol' 69 | }); 70 | 71 | // Using Ext.ux.WebSocketManager 72 | Ext.ux.WebSocketManager.register (ws); 73 | ``` 74 | 75 | ## Communications supported 76 | ### Pure text communication 77 | The communication is text-only, without objects or any other kind of data. 78 | 79 | ```javascript 80 | var websocket = Ext.create ('Ext.ux.WebSocket', { 81 | url: 'ws://localhost:8888' , 82 | listeners: { 83 | open: function (ws) { 84 | console.log ('The websocket is ready to use'); 85 | ws.send ('This is a simple text'); 86 | } , 87 | close: function (ws) { 88 | console.log ('The websocket is closed!'); 89 | } , 90 | error: function (ws, error) { 91 | Ext.Error.raise (error); 92 | } , 93 | message: function (ws, message) { 94 | console.log ('A new message is arrived: ' + message); 95 | } 96 | } 97 | }); 98 | ``` 99 | 100 | ### Pure event-driven communication 101 | The communication is event-driven: an event and a String or Object are sent and the websocket handles different events. 102 | 103 | ```javascript 104 | var websocket = Ext.create ('Ext.ux.WebSocket', { 105 | url: 'ws://localhost:8888' , 106 | listeners: { 107 | open: function (ws) { 108 | console.log ('The websocket is ready to use'); 109 | ws.send ('init', 'This is a simple text'); 110 | ws.send ('and continue', { 111 | 'my': 'data' , 112 | 'your': 'data' 113 | }); 114 | } , 115 | close: function (ws) { 116 | console.log ('The websocket is closed!'); 117 | } 118 | } 119 | }); 120 | 121 | // A 'stop' event is sent from the server 122 | // 'data' has 'cmd' and 'msg' fields 123 | websocket.on ('stop', function (data) { 124 | console.log ('Command: ' + data.cmd); 125 | console.log ('Message: ' + data.msg); 126 | }); 127 | ``` 128 | 129 | ### Mixed communication 130 | The communication is mixed: it can handles text-only and event-driven communication. 131 | 132 | ```javascript 133 | var websocket = Ext.create ('Ext.ux.WebSocket', { 134 | url: 'ws://localhost:8888' , 135 | listeners: { 136 | open: function (ws) { 137 | console.log ('The websocket is ready to use'); 138 | ws.send ('This is only-text message'); 139 | ws.send ('init', 'This is a simple text'); 140 | ws.send ('and continue', { 141 | 'my': 'data' , 142 | 'your': 'data' 143 | }); 144 | } , 145 | close: function (ws) { 146 | console.log ('The websocket is closed!'); 147 | } , 148 | message: function (ws, message) { 149 | console.log ('Text-only message arrived is: ' + message); 150 | } 151 | } 152 | }); 153 | 154 | // A 'stop' event is sent from the server 155 | // 'data' has 'cmd' and 'msg' fields 156 | websocket.on ('stop', function (data) { 157 | console.log ('Command: ' + data.cmd); 158 | console.log ('Message: ' + data.msg); 159 | }); 160 | ``` 161 | 162 | ## Ext.ux.WebSocketManager features 163 | Here's an example of the manager: 164 | 165 | ```javascript 166 | var ws1 = Ext.create ('Ext.ux.WebSocket', { 167 | url: 'ws://localhost:8888' 168 | }); 169 | 170 | Ext.ux.WebSocketManager.register (ws1); 171 | 172 | var ws2 = Ext.create ('Ext.ux.WebSocket', { 173 | url: 'ws://localhost:8900' 174 | }); 175 | 176 | Ext.ux.WebSocketManager.register (ws2); 177 | 178 | var ws3 = Ext.create ('Ext.ux.WebSocket', { 179 | url: 'ws://localhost:8950' 180 | }); 181 | 182 | Ext.ux.WebSocketManager.register (ws3); 183 | 184 | Ext.ux.WebSocketManager.listen ('system shutdown', function (ws, data) { 185 | Ext.Msg.show ({ 186 | title: 'System Shutdown' , 187 | msg: data , 188 | icon: Ext.Msg.WARNING , 189 | buttons: Ext.Msg.OK 190 | }); 191 | }); 192 | 193 | // This will be handled by everyone 194 | Ext.ux.WebSocketManager.broadcast ('system shutdown', 'BROADCAST: the system will shutdown in few minutes.'); 195 | 196 | Ext.ux.WebSocketManager.closeAll (); 197 | 198 | Ext.ux.WebSocketManager.unregister (ws1); 199 | Ext.ux.WebSocketManager.unregister (ws2); 200 | Ext.ux.WebSocketManager.unregister (ws3); 201 | ``` 202 | 203 | ## Cool Configurations 204 | Follows some cool configurations to customize `Ext.ux.WebSocket` 205 | 206 | ### autoReconnect 207 | By default, `Ext.ux.WebSocket` tries to re-open the connection with an internal task if it gets closed: it tries every 5 seconds. 208 | Now, if you want to disable this behaviour, set autoReconnect to false: 209 | 210 | ```javascript 211 | var websocket = Ext.create ('Ext.ux.WebSocket', { 212 | url: 'ws://localhost:8888' , 213 | autoReconnect: false 214 | }); 215 | ``` 216 | 217 | In this case, when the server listens again, the websocket won't estabilish the connection. 218 | Otherwise, if you want to change the reconnect timeslice, set autoReconnectionInterval in milliseconds: 219 | 220 | ```javascript 221 | var websocket = Ext.create ('Ext.ux.WebSocket', { 222 | url: 'ws://localhost:8888' , 223 | autoReconnect: true , 224 | autoReconnectInterval: 1000 225 | }); 226 | ``` 227 | 228 | While in this case, the websocket tries to re-open the connection with the server every second. 229 | 230 | ### lazyConnection 231 | Setting this config to true it will let the websocket to open the connection after its initialization: 232 | 233 | ```javascript 234 | var websocket = Ext.create ('Ext.ux.WebSocket', { 235 | url: 'ws://localhost:8888' , 236 | lazyConnection: true 237 | }); 238 | 239 | // other stuff 240 | 241 | websocket.open (); 242 | ``` 243 | 244 | When the **open** method is called, the websocket open the connection with the server. 245 | By default, this config is set to **false** 246 | 247 | ### keepUnsentMessages 248 | The connection gets closed by the server but the websocket still sends messages to it. 249 | By default, those messages are discarded but if you want to keep them and then send them back after the connection is re-estabilished, just set keepUnsentMessages to true: 250 | 251 | ```javascript 252 | var websocket = Ext.create ('Ext.ux.WebSocket', { 253 | url: 'ws://localhost:8888' , 254 | keepUnsentMessages: true 255 | }); 256 | ``` 257 | 258 | The websocket will send every unsent messages in one shot. 259 | 260 | ## Run the demo 261 | First of all, you need [**NodeJS**](http://nodejs.org/) and [**NPM**](https://www.npmjs.org/). 262 | 263 | Then, install every dependencies: 264 | 265 | ```bash 266 | $ npm install 267 | ``` 268 | 269 | Open a websocket for each port you want: 270 | 271 | ```bash 272 | $ node demo/server.js 9001 9002 9003 273 | ``` 274 | 275 | Now, you have three websockets listening at 9001, 9002 and 9003 port on the server side! 276 | Then, type in the address bar of your browser: **http://localhost/ExtJS-WebSocket/demo** and play the demo ;) 277 | 278 | ## Documentation 279 | You can build the documentation (like ExtJS Docs) with [**jsduck**](https://github.com/senchalabs/jsduck): 280 | 281 | ```bash 282 | $ jsduck ux --output /var/www/docs 283 | ``` 284 | 285 | It will make the documentation into docs dir and it will be visible at: http://localhost/docs 286 | 287 | ## License 288 | The MIT License (MIT) 289 | 290 | Copyright (c) 2013 Vincenzo Ferrari 291 | 292 | 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: 293 | 294 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 295 | 296 | 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. 297 | -------------------------------------------------------------------------------- /WebSocket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.WebSocket 3 | * @author Vincenzo Ferrari 4 | * 5 | * Wrapper for HTML5 WebSocket 6 | * 7 | * This class provide an interface for HTML5 WebSocket. 8 | * 9 | *

Pure text communication

10 | * The communication is text-only, without objects or any other kind of data. 11 | * 12 | * var websocket = Ext.create ('Ext.ux.WebSocket', { 13 | * url: 'ws://localhost:8888' , 14 | * listeners: { 15 | * open: function (ws) { 16 | * console.log ('The websocket is ready to use'); 17 | * ws.send ('This is a simple text'); 18 | * } , 19 | * close: function (ws) { 20 | * console.log ('The websocket is closed!'); 21 | * } , 22 | * error: function (ws, error) { 23 | * Ext.Error.raise (error); 24 | * } , 25 | * message: function (ws, message) { 26 | * console.log ('A new message is arrived: ' + message); 27 | * } 28 | * } 29 | * }); 30 | * 31 | *

Pure event-driven communication

32 | * The communication is event-driven: an event and a String or Object are sent and the websocket handles different events. 33 | * 34 | * var websocket = Ext.create ('Ext.ux.WebSocket', { 35 | * url: 'ws://localhost:8888' , 36 | * listeners: { 37 | * open: function (ws) { 38 | * console.log ('The websocket is ready to use'); 39 | * ws.send ('init', 'This is a simple text'); 40 | * ws.send ('and continue', { 41 | * 'my': 'data' , 42 | * 'your': 'data' 43 | * }); 44 | * } , 45 | * close: function (ws) { 46 | * console.log ('The websocket is closed!'); 47 | * } 48 | * } 49 | * }); 50 | * 51 | * // A 'stop' event is sent from the server 52 | * // 'data' has 'cmd' and 'msg' fields 53 | * websocket.on ('stop', function (data) { 54 | * console.log ('Command: ' + data.cmd); 55 | * console.log ('Message: ' + data.msg); 56 | * }); 57 | * 58 | *

Mixed event-driven and text communication

59 | * The communication is mixed: it can handles text-only and event-driven communication. 60 | * 61 | * var websocket = Ext.create ('Ext.ux.WebSocket', { 62 | * url: 'ws://localhost:8888' , 63 | * listeners: { 64 | * open: function (ws) { 65 | * console.log ('The websocket is ready to use'); 66 | * ws.send ('This is only-text message'); 67 | * ws.send ('init', 'This is a simple text'); 68 | * ws.send ('and continue', { 69 | * 'my': 'data' , 70 | * 'your': 'data' 71 | * }); 72 | * } , 73 | * close: function (ws) { 74 | * console.log ('The websocket is closed!'); 75 | * } , 76 | * message: function (ws, message) { 77 | * console.log ('Text-only message arrived is: ' + message); 78 | * } 79 | * } 80 | * }); 81 | * 82 | * // A 'stop' event is sent from the server 83 | * // 'data' has 'cmd' and 'msg' fields 84 | * websocket.on ('stop', function (data) { 85 | * console.log ('Command: ' + data.cmd); 86 | * console.log ('Message: ' + data.msg); 87 | * }); 88 | */ 89 | 90 | Ext.define('Ext.ux.WebSocket', { 91 | alias: 'websocket', 92 | 93 | mixins: { 94 | observable: 'Ext.util.Observable' 95 | }, 96 | 97 | requires: ['Ext.util.TaskManager', 'Ext.util.Memento'], 98 | 99 | /** 100 | * @event open 101 | * Fires after the websocket has been connected. 102 | * @param {Ext.ux.WebSocket} this The websocket 103 | */ 104 | 105 | /** 106 | * @event error 107 | * Fires after an error occured 108 | * @param {Ext.ux.WebSocket} this The websocket 109 | * @param {Object} error The error object to display 110 | */ 111 | 112 | /** 113 | * @event close 114 | * Fires after the websocket has been disconnected. 115 | * @param {Ext.ux.WebSocket} this The websocket 116 | */ 117 | 118 | /** 119 | * @event message 120 | * Fires after a message is arrived from the server. 121 | * @param {Ext.ux.WebSocket} this The websocket 122 | * @param {String/Object} message The message arrived 123 | */ 124 | 125 | config: { 126 | /** 127 | * @cfg {String} url (required) The URL to connect 128 | */ 129 | url: '', 130 | 131 | /** 132 | * @cfg {String} protocol The protocol to use in the connection 133 | */ 134 | protocol: null, 135 | 136 | /** 137 | * @cfg {String} communicationType The type of communication. 'both' (default) for event-driven and pure-text communication, 'event' for only event-driven and 'text' for only pure-text. 138 | */ 139 | communicationType: 'both', 140 | 141 | /** 142 | * @cfg {Boolean} autoReconnect If the connection is closed by the server, it tries to re-connect again. The execution interval time of this operation is specified in autoReconnectInterval 143 | */ 144 | autoReconnect: true, 145 | 146 | /** 147 | * @cfg {Int} autoReconnectInterval Execution time slice of the autoReconnect operation, specified in milliseconds. 148 | */ 149 | autoReconnectInterval: 5000, 150 | 151 | /** 152 | * @cfg {Boolean} lazyConnection Connect the websocket after the initialization with the open method 153 | */ 154 | lazyConnection: false, 155 | 156 | /** 157 | * @cfg {Boolean} keepUnsentMessages Keep unsent messages and try to send them back after the connection is open again 158 | */ 159 | keepUnsentMessages: false 160 | }, 161 | 162 | /** 163 | * @property {Number} CONNECTING 164 | * @readonly 165 | * The connection is not yet open. 166 | */ 167 | CONNECTING: 0, 168 | 169 | /** 170 | * @property {Number} OPEN 171 | * @readonly 172 | * The connection is open and ready to communicate. 173 | */ 174 | OPEN: 1, 175 | 176 | /** 177 | * @property {Number} CLOSING 178 | * @readonly 179 | * The connection is in the process of closing. 180 | */ 181 | CLOSING: 2, 182 | 183 | /** 184 | * @property {Number} CLOSED 185 | * @readonly 186 | * The connection is closed or couldn't be opened. 187 | */ 188 | CLOSED: 3, 189 | 190 | /** 191 | * @property {Object} memento 192 | * @private 193 | * Internal memento 194 | */ 195 | memento: {}, 196 | 197 | /** 198 | * @property {Array} memento 199 | * @private 200 | * Internal queue of unsent messages 201 | */ 202 | messageQueue: [], 203 | 204 | /** 205 | * Creates new WebSocket 206 | * @param {String/Object} config The configuration options may be specified as follows: 207 | * 208 | * // with a configuration set 209 | * var config = { 210 | * url: 'your_url' , 211 | * protocol: 'your_protocol' 212 | * }; 213 | * 214 | * var ws = Ext.create ('Ext.ux.WebSocket', config); 215 | * 216 | * // or with websocket url only 217 | * var ws = Ext.create ('Ext.ux.WebSocket', 'ws://localhost:30000'); 218 | * 219 | * @return {Ext.ux.WebSocket} An instance of Ext.ux.WebSocket or null if an error occurred. 220 | */ 221 | constructor: function (cfg) { 222 | var me = this; 223 | 224 | // Raises an error if no url is given 225 | if (Ext.isEmpty(cfg)) { 226 | Ext.Error.raise('URL for the websocket is required!'); 227 | return null; 228 | } 229 | 230 | // Allows initialization with string 231 | // e.g.: Ext.create ('Ext.ux.WebSocket', 'ws://localhost:8888'); 232 | if (typeof cfg === 'string') { 233 | cfg = { 234 | url: cfg 235 | }; 236 | } 237 | 238 | me.initConfig(cfg); 239 | me.mixins.observable.constructor.call(me, cfg); 240 | 241 | try { 242 | // Initializes internal websocket 243 | if (!me.getLazyConnection()) me.initWebsocket(); 244 | 245 | me.memento = Ext.create('Ext.util.Memento'); 246 | me.memento.capture('autoReconnect', me); 247 | } 248 | catch (err) { 249 | Ext.Error.raise(err); 250 | return null; 251 | } 252 | 253 | return me; 254 | }, 255 | 256 | /** 257 | * @method isReady 258 | * Returns if the websocket connection is up or not 259 | * @return {Boolean} True if the connection is up, False otherwise 260 | */ 261 | isReady: function () { 262 | return this.getStatus() === this.OPEN; 263 | }, 264 | 265 | /** 266 | * @method getStatus 267 | * Returns the current status of the websocket 268 | * @return {Number} The current status of the websocket (0: connecting, 1: open, 2: closed) 269 | */ 270 | getStatus: function () { 271 | return this.ws.readyState; 272 | }, 273 | 274 | /** 275 | * @method close 276 | * Closes the websocket and kills the autoreconnect task, if exists 277 | * @return {Ext.ux.WebSocket} The websocket 278 | */ 279 | close: function () { 280 | var me = this; 281 | 282 | if (me.autoReconnectTask) { 283 | Ext.TaskManager.stop(me.autoReconnectTask); 284 | delete me.autoReconnectTask; 285 | } 286 | // Deactivate autoReconnect until the websocket is open again 287 | me.setAutoReconnect(false); 288 | 289 | me.ws.close(); 290 | 291 | return me; 292 | }, 293 | 294 | /** 295 | * @method open 296 | * Re/Open the websocket 297 | * @return {Ext.ux.WebSocket} The websocket 298 | */ 299 | open: function () { 300 | var me = this; 301 | 302 | // Restore autoReconnect initial value 303 | me.memento.restore('autoReconnect', false, me); 304 | me.initWebsocket(); 305 | 306 | return me; 307 | }, 308 | 309 | /** 310 | * @method send 311 | * Sends a message. 312 | * This method is bind at run-time level because it changes on the websocket initial configuration. 313 | * It supports three kind of communication: 314 | * 315 | * 1. text-only 316 | * Syntax: ws.send (string); 317 | * Example: ws.send ('hello world!'); 318 | * 2. event-driven 319 | * Syntax: ws.send (event, string/object); 320 | * Example 1: ws.send ('greetings', 'hello world!'); 321 | * Example 2: ws.send ('greetings', {text: 'hello world!'}); 322 | * 3. hybrid (text and event) 323 | * It uses both: see examples above 324 | * @param {String/Object} message Can be a single text message or an association of event/message. 325 | */ 326 | send: function () { 327 | }, 328 | 329 | /** 330 | * @method initWebsocket 331 | * Internal websocket initialization 332 | * @private 333 | */ 334 | initWebsocket: function () { 335 | var me = this; 336 | 337 | me.ws = Ext.isEmpty(me.getProtocol()) ? new WebSocket(me.getUrl()) : new WebSocket(me.getUrl(), me.getProtocol()); 338 | 339 | me.ws.onopen = function (evt) { 340 | // Kills the auto reconnect task 341 | // It will be reactivated at the next onclose event 342 | if (me.autoReconnectTask) { 343 | Ext.TaskManager.stop(me.autoReconnectTask); 344 | delete me.autoReconnectTask; 345 | } 346 | 347 | // Flush unset messages 348 | if (me.getKeepUnsentMessages() && me.messageQueue.length > 0) { 349 | while (me.messageQueue.length > 0) { 350 | // Avoid infinite loop into safeSend method 351 | if (me.isReady()) me.safeSend(me.messageQueue.shift()); 352 | else break; 353 | } 354 | } 355 | 356 | me.fireEvent('open', me); 357 | }; 358 | 359 | me.ws.onerror = function (error) { 360 | me.fireEvent('error', me, error); 361 | }; 362 | 363 | me.ws.onclose = function (evt) { 364 | me.fireEvent('close', me); 365 | 366 | // Setups the auto reconnect task, just one 367 | if (me.getAutoReconnect() && (typeof me.autoReconnectTask === 'undefined')) { 368 | me.autoReconnectTask = Ext.TaskManager.start({ 369 | run: function () { 370 | // It reconnects only if it's disconnected 371 | if (me.getStatus() === me.CLOSED) { 372 | me.initWebsocket(); 373 | } 374 | }, 375 | interval: me.getAutoReconnectInterval() 376 | }); 377 | } 378 | }; 379 | 380 | if (me.getCommunicationType() === 'both') { 381 | me.ws.onmessage = Ext.bind(me.receiveBothMessage, this); 382 | me.send = Ext.bind(me.sendBothMessage, this); 383 | } 384 | else if (me.getCommunicationType() === 'event') { 385 | me.ws.onmessage = Ext.bind(me.receiveEventMessage, this); 386 | me.send = Ext.bind(me.sendEventMessage, this); 387 | } 388 | else { 389 | me.ws.onmessage = Ext.bind(me.receiveTextMessage, this); 390 | me.send = Ext.bind(me.sendTextMessage, this); 391 | } 392 | }, 393 | 394 | /** 395 | * @method flush 396 | * It sends every message given to the websocket, checking first if is there any connection 397 | * If there's no connection, it enqueues the message and flushes it later 398 | * @param {String} Data to send 399 | * @return {Ext.ux.WebSocket} The websocket 400 | * @private 401 | */ 402 | safeSend: function (data) { 403 | var me = this; 404 | 405 | if (me.isReady()) me.ws.send(data); 406 | else if (me.getKeepUnsentMessages()) me.messageQueue.push(data); 407 | 408 | return me; 409 | }, 410 | 411 | /** 412 | * @method receiveBothMessage 413 | * It catches every event-driven and pure text messages incoming from the server 414 | * @param {Object} message Message incoming from the server 415 | * @private 416 | */ 417 | receiveBothMessage: function (message) { 418 | var me = this; 419 | 420 | try { 421 | /* 422 | message.data : JSON encoded message 423 | msg.event : event to be raise 424 | msg.data : data to be handle 425 | */ 426 | var msg = Ext.JSON.decode(message.data); 427 | me.fireEvent(msg.event, me, msg.data); 428 | me.fireEvent('message', me, msg); 429 | } 430 | catch (err) { 431 | if (Ext.isString(message.data)) me.fireEvent(message.data, me, message.data); 432 | // Message event is always sent 433 | me.fireEvent('message', me, message.data); 434 | } 435 | }, 436 | 437 | /** 438 | * @method receiveEventMessage 439 | * It catches every event-driven messages incoming from the server 440 | * @param {Object} message Message incoming from the server 441 | * @private 442 | */ 443 | receiveEventMessage: function (message) { 444 | var me = this; 445 | 446 | try { 447 | var msg = Ext.JSON.decode(message.data); 448 | me.fireEvent(msg.event, me, msg.data); 449 | me.fireEvent('message', me, msg); 450 | } 451 | catch (err) { 452 | Ext.Error.raise(err); 453 | } 454 | }, 455 | 456 | /** 457 | * @method receiveTextMessage 458 | * It catches every pure text messages incoming from the server 459 | * @param {Object} message Message incoming from the server 460 | * @private 461 | */ 462 | receiveTextMessage: function (message) { 463 | var me = this; 464 | 465 | try { 466 | me.fireEvent(message, me, message); 467 | // Message event is always sent 468 | me.fireEvent('message', me, message); 469 | } 470 | catch (err) { 471 | Ext.Error.raise(err); 472 | } 473 | }, 474 | 475 | /** 476 | * @method sendBothMessage 477 | * It sends both pure text and event-driven messages to the server 478 | * @param {String/String[]} events Message(s) or event(s) to send to the server 479 | * @param {String/Object} data Message to send to the server, associated to its event 480 | * @return {Ext.ux.WebSocket} The websocket 481 | * @private 482 | */ 483 | sendBothMessage: function (events, data) { 484 | var me = this; 485 | 486 | // Treats it as normal message 487 | if (arguments.length === 1) { 488 | if (Ext.isString(events)) me.safeSend(events); 489 | else Ext.Error.raise('String expected!'); 490 | } 491 | // Treats it as event-driven message 492 | else if (arguments.length >= 2) { 493 | events = Ext.isString(events) ? [events] : events; 494 | 495 | for (var i = 0; i < events.length; i++) { 496 | var msg = { 497 | event: events[i], 498 | data: data 499 | }; 500 | 501 | me.safeSend(Ext.JSON.encode(msg)); 502 | } 503 | } 504 | 505 | return me; 506 | }, 507 | 508 | /** 509 | * @method sendEventMessage 510 | * It sends event-driven messages to the server 511 | * @param {String/String[]} events Event(s) to send to the server 512 | * @param {String/Object} data Message to send to the server, associated to its event(s) 513 | * @return {Ext.ux.WebSocket} The websocket 514 | * @private 515 | */ 516 | sendEventMessage: function (events, data) { 517 | var me = this; 518 | 519 | events = Ext.isString(events) ? [events] : events; 520 | 521 | for (var i = 0; i < events.length; i++) { 522 | var msg = { 523 | event: events[i], 524 | data: data 525 | }; 526 | 527 | me.safeSend(Ext.JSON.encode(msg)); 528 | } 529 | 530 | return me; 531 | }, 532 | 533 | /** 534 | * @method sendTextMessage 535 | * It sends pure text messages to the server 536 | * @param {String} event Message to send to the server 537 | * @return {Ext.ux.WebSocket} The websocket 538 | * @private 539 | */ 540 | sendTextMessage: function (event) { 541 | var me = this; 542 | 543 | me.safeSend(event); 544 | 545 | return me; 546 | } 547 | }); 548 | -------------------------------------------------------------------------------- /WebSocketManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.WebSocketManager 3 | * @author Vincenzo Ferrari 4 | * @singleton 5 | * 6 | * Manager of Ext.ux.WebSocket 7 | * 8 | * This singleton provide some useful functions to use for many websockets. 9 | * 10 | * var ws1 = Ext.create ('Ext.ux.WebSocket', { 11 | * url: 'ws://localhost:8888' 12 | * }); 13 | * 14 | * Ext.ux.WebSocketManager.register (ws1); 15 | * 16 | * var ws2 = Ext.create ('Ext.ux.WebSocket', { 17 | * url: 'ws://localhost:8900' 18 | * }); 19 | * 20 | * Ext.ux.WebSocketManager.register (ws2); 21 | * 22 | * var ws3 = Ext.create ('Ext.ux.WebSocket', { 23 | * url: 'ws://localhost:8950' 24 | * }); 25 | * 26 | * Ext.ux.WebSocketManager.register (ws3); 27 | * 28 | * Ext.ux.WebSocketManager.listen ('system shutdown', function (ws, data) { 29 | * Ext.Msg.show ({ 30 | * title: 'System Shutdown' , 31 | * msg: data , 32 | * icon: Ext.Msg.WARNING , 33 | * buttons: Ext.Msg.OK 34 | * }); 35 | * }); 36 | * 37 | * Ext.ux.WebSocketManager.broadcast ('system shutdown', 'BROADCAST: the system will shutdown in few minutes.'); 38 | * 39 | * Ext.ux.WebSocketManager.closeAll (); 40 | * 41 | * Ext.ux.WebSocketManager.unregister (ws1); 42 | * Ext.ux.WebSocketManager.unregister (ws2); 43 | * Ext.ux.WebSocketManager.unregister (ws3); 44 | */ 45 | Ext.define('Ext.ux.WebSocketManager', { 46 | singleton: true, 47 | 48 | /** 49 | * @property {Ext.util.HashMap} wsList 50 | * @private 51 | */ 52 | wsList: Ext.create('Ext.util.HashMap'), 53 | 54 | /** 55 | * @method register 56 | * Registers one or more Ext.ux.WebSocket 57 | * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to register. Could be only one. 58 | */ 59 | register: function (websockets) { 60 | var me = this; 61 | 62 | // Changes websockets into an array in every case 63 | if (Ext.isObject(websockets)) websockets = [websockets]; 64 | 65 | Ext.each(websockets, function (websocket) { 66 | if (!Ext.isEmpty(websocket.url)) me.wsList.add(websocket.url, websocket); 67 | }); 68 | }, 69 | 70 | /** 71 | * @method contains 72 | * Checks if a websocket is already registered or not 73 | * @param {Ext.ux.WebSocket} websocket The WebSocket to find 74 | * @return {Boolean} True if the websocket is already registered, False otherwise 75 | */ 76 | contains: function (websocket) { 77 | return this.wsList.containsKey(websocket.url); 78 | }, 79 | 80 | /** 81 | * @method get 82 | * Retrieves a registered websocket by its url 83 | * @param {String} url The url of the websocket to search 84 | * @return {Ext.ux.WebSocket} The websocket or undefined 85 | */ 86 | get: function (url) { 87 | return this.wsList.get(url); 88 | }, 89 | 90 | /** 91 | * @method each 92 | * Executes a function for each registered websocket 93 | * @param {Function} fn The function to execute 94 | */ 95 | each: function (fn) { 96 | this.wsList.each(function (url, websocket, len) { 97 | fn(websocket); 98 | }); 99 | }, 100 | 101 | /** 102 | * @method unregister 103 | * Unregisters one or more Ext.ux.WebSocket 104 | * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to unregister 105 | */ 106 | unregister: function (websockets) { 107 | var me = this; 108 | 109 | if (Ext.isObject(websockets)) websockets = [websockets]; 110 | 111 | Ext.each(websockets, function (websocket) { 112 | if (me.wsList.containsKey(websocket.url)) me.wsList.removeAtKey(websocket.url); 113 | }); 114 | }, 115 | 116 | /** 117 | * @method broadcast 118 | * Sends a message to each websocket 119 | * @param {String} event The event to raise 120 | * @param {String/Object} message The data to send 121 | */ 122 | broadcast: function (event, message) { 123 | this.multicast([], event, message); 124 | }, 125 | 126 | /** 127 | * @method multicast 128 | * Sends a message to each websocket, except those specified 129 | * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets An array of websockets to take off the communication 130 | * @param {String} event The event to raise 131 | * @param {String/Object} data The data to send 132 | */ 133 | multicast: function (websockets, event, data) { 134 | this.getExcept(websockets).each(function (url, websocket, len) { 135 | if (websocket.isReady()) { 136 | if (Ext.isEmpty(data)) websocket.send(event); 137 | else websocket.send(event, data); 138 | } 139 | }); 140 | }, 141 | 142 | /** 143 | * @method listen 144 | * Adds an handler for events given to each registered websocket 145 | * @param {String/String[]} events Events to listen 146 | * @param {Function} handler The events' handler 147 | */ 148 | listen: function (events, handler) { 149 | if (Ext.isString(events)) events = [events]; 150 | 151 | this.wsList.each(function (url, websocket, len) { 152 | Ext.each(events, function (event) { 153 | websocket.on(event, handler); 154 | }); 155 | }); 156 | }, 157 | 158 | /** 159 | * @method listenExcept 160 | * Adds an handler for events given to each registered websocket, except websockets given 161 | * @param {String/String[]} events Events to listen 162 | * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to exclude 163 | * @param {Function} handler The events' handler 164 | */ 165 | listenExcept: function (events, websockets, handler) { 166 | if (Ext.isString(events)) events = [events]; 167 | 168 | this.getExcept(websockets).each(function (url, websocket, len) { 169 | Ext.each(events, function (event) { 170 | websocket.on(event, handler); 171 | }); 172 | }); 173 | }, 174 | 175 | /** 176 | * @method getExcept 177 | * Retrieves registered websockets except the input 178 | * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to exclude 179 | * @return {Ext.util.HashMap} Registered websockets except the input 180 | * @private 181 | */ 182 | getExcept: function (websockets) { 183 | if (Ext.isObject(websockets)) websockets = [websockets]; 184 | 185 | var list = this.wsList.clone(); 186 | 187 | // Exclude websockets from the communication 188 | Ext.each(websockets, function (websocket) { 189 | list.removeAtKey(websocket.url); 190 | }); 191 | 192 | return list; 193 | }, 194 | 195 | /** 196 | * @method closeAll 197 | * Closes any registered websocket 198 | */ 199 | closeAll: function () { 200 | var me = this; 201 | 202 | me.wsList.each(function (url, websocket, len) { 203 | websocket.close(); 204 | me.unregister(websocket); 205 | }); 206 | } 207 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ext.ux.WebSocket", 3 | "version": "v1.0.0", 4 | "homepage": "https://github.com/wilk/ExtJS-WebSocket", 5 | "authors": [ 6 | "Vincenzo (Wilk) Ferrari " 7 | ], 8 | "description": "Ext.ux.WebSocket is an extension to manage HTML5 WebSocket with ExtJS and SenchaTouch", 9 | "main": ["WebSocket.js", "WebSocketManager.js"], 10 | "keywords": [ 11 | "extjs", 12 | "senchatouch", 13 | "sencha", 14 | "websocket", 15 | "html5" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests", 24 | "demo", 25 | "ux", 26 | "package.json", 27 | "bower.json", 28 | "Gruntfile.js", 29 | "*.min.js" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | Ext.Loader.setConfig ({ 2 | enabled: true, 3 | paths: { 4 | 'Ext.ux.WebSocket': '../WebSocket.js' , 5 | 'Ext.ux.WebSocketManager': '../WebSocketManager.js' 6 | } 7 | }); 8 | 9 | Ext.require (['Ext.ux.WebSocket', 'Ext.ux.WebSocketManager']); 10 | 11 | Ext.define ('DEMO.view.OpenConnection', { 12 | extend: 'Ext.panel.Panel' , 13 | 14 | title: 'Open a new connection' , 15 | width: 300 , 16 | layout: 'anchor' , 17 | 18 | openConnection: function (obj) { 19 | var url = obj.up('panel').down('textfield').getValue (); 20 | 21 | var ws = Ext.create ('Ext.ux.WebSocket', { 22 | url: url , 23 | listeners: { 24 | open: function (ws) { 25 | if (Ext.get(ws.url)) Ext.get(ws.url).dom.innerHTML += '> WebSocket just open!
'; 26 | } , 27 | message: function (ws, data) { 28 | Ext.get(ws.url).dom.innerHTML += '> ' + data + '
'; 29 | } , 30 | close: function (ws) { 31 | var panel = Ext.getCmp ('panel' + ws.url); 32 | 33 | if ((panel != null) || (panel != undefined)) { 34 | panel.destroy (); 35 | } 36 | } 37 | } 38 | }); 39 | 40 | // Connection panel 41 | var panel = Ext.create ('Ext.panel.Panel', { 42 | title: url , 43 | ws: ws , 44 | id: 'panel' + url , 45 | 46 | layout: 'anchor' , 47 | 48 | bodyPadding: 5 , 49 | collapsible: true , 50 | 51 | items: [{ 52 | xtype: 'container' , 53 | html: 'Incoming from the server:
' 54 | } , { 55 | xtype: 'textarea' , 56 | labelAlign: 'top' , 57 | fieldLabel: 'Send a message' , 58 | anchor: '100%' 59 | }] , 60 | 61 | buttons: [{ 62 | text: 'Reset' , 63 | handler: function (btn, evt) { 64 | btn.up('panel').down('textarea').reset (); 65 | } 66 | } , { 67 | text: 'Send' , 68 | handler: function (btn, evt) { 69 | btn.up('panel').ws.send(btn.up('panel').down('textarea').getValue ()); 70 | } 71 | }] , 72 | 73 | dockedItems: { 74 | xtype: 'toolbar' , 75 | dock: 'top' , 76 | defaults: { 77 | xtype: 'button' 78 | } , 79 | items: [{ 80 | // Registers to Ext.ux.WebSocketManager 81 | text: 'Register' , 82 | handler: function (btn, evt) { 83 | if (btn.getText () === 'Register') { 84 | Ext.ux.WebSocketManager.register (btn.up('toolbar').up('panel').ws); 85 | btn.setText ('Unregister'); 86 | } 87 | else { 88 | Ext.ux.WebSocketManager.unregister (btn.up('toolbar').up('panel').ws); 89 | btn.setText ('Register'); 90 | } 91 | } 92 | } , { 93 | text: 'Close' , 94 | handler: function (btn, evt) { 95 | btn.up('toolbar').up('panel').ws.close (); 96 | btn.up('toolbar').up('panel').destroy (); 97 | } 98 | }] 99 | } 100 | }); 101 | 102 | Ext.getCmp('connections').add (panel); 103 | } , 104 | 105 | items: [{ 106 | xtype: 'textfield' , 107 | anchor: '100%' , 108 | fieldLabel: 'URL' , 109 | labelAlign: 'top' , 110 | listeners: { 111 | specialKey: function (tf, evt) { 112 | if (evt.getKey () === evt.ENTER) { 113 | this.up('panel').openConnection (tf); 114 | } 115 | } 116 | } 117 | }] , 118 | 119 | buttons: [{ 120 | text: 'Reset' , 121 | handler: function (btn, evt) { 122 | btn.up('panel').down('textfield').reset (); 123 | } 124 | } , { 125 | text: 'Open' , 126 | handler: function (btn) { 127 | btn.up('panel').openConnection (btn); 128 | } 129 | }] 130 | }); 131 | 132 | Ext.define ('DEMO.view.BroadcastConnection', { 133 | extend: 'Ext.panel.Panel' , 134 | 135 | title: 'Broadcast Connection' , 136 | width: 500 , 137 | layout: 'fit' , 138 | 139 | items: [{ 140 | xtype: 'textarea' , 141 | fieldLabel: 'Broadcast a message' , 142 | labelAlign: 'top' , 143 | }] , 144 | 145 | buttons: [{ 146 | text: 'Close any connections' , 147 | handler: function (btn, evt) { 148 | Ext.ux.WebSocketManager.closeAll (); 149 | } 150 | } , '->' , { 151 | text: 'Reset' , 152 | handler: function (btn, evt) { 153 | btn.up('panel').down('textarea').reset (); 154 | } 155 | } , { 156 | // Broadcasts a message 157 | text: 'Send' , 158 | handler: function (btn, evt) { 159 | Ext.ux.WebSocketManager.broadcast ('BROADCAST: ' + btn.up('panel').down('textarea').getValue ()); 160 | } 161 | }] 162 | }); 163 | 164 | Ext.onReady (function () { 165 | var oc = Ext.create ('DEMO.view.OpenConnection'); 166 | var bc = Ext.create ('DEMO.view.BroadcastConnection'); 167 | 168 | Ext.create ('Ext.container.Container', { 169 | renderTo: Ext.getBody () , 170 | 171 | layout: { 172 | type: 'hbox' , 173 | align: 'middle' , 174 | pack: 'center' 175 | } , 176 | 177 | items: [{ 178 | xtype: 'container' , 179 | layout: { 180 | type: 'vbox' , 181 | align: 'stretch' 182 | } , 183 | width: 800 , 184 | 185 | items: [{ 186 | xtype: 'panel', 187 | 188 | title: 'Demo Ext.ux.WebSocket and Ext.ux.WebSocketManager' , 189 | 190 | layout: { 191 | type: 'vbox' , 192 | align: 'stretch' 193 | } , 194 | 195 | items: [{ 196 | xtype: 'container' , 197 | layout: { 198 | type: 'hbox' , 199 | align: 'stretch' 200 | } , 201 | defaults: { 202 | bodyPadding: 5 203 | } , 204 | items: [oc, bc] 205 | } , { 206 | xtype: 'panel' , 207 | title: 'Connections' , 208 | id: 'connections' , 209 | layout: { 210 | type: 'vbox' , 211 | align: 'stretch' 212 | } 213 | }] 214 | }] 215 | }] 216 | }); 217 | }); 218 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ExtJS WebSocket 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.argv.length < 3) { 4 | console.log('Usage: $ node server.js '); 5 | console.log('Example: $ node server.js 9001 9002 9003'); 6 | console.log('Exit'); 7 | 8 | process.exit(); 9 | } 10 | 11 | var WebSocketServer = require('ws').Server; 12 | 13 | process.argv.forEach(function (val, index) { 14 | if (index > 1) { 15 | var wss = new WebSocketServer({port: val}, function () { 16 | console.log('WebSocketServer :: listening on port ' + val); 17 | }); 18 | 19 | wss.on('connection', function (ws) { 20 | console.log('WebSocket[' + val + '] :: connected'); 21 | 22 | ws.on('message', function (msg) { 23 | console.log('WebSocket[' + val + '] :: ' + msg); 24 | 25 | ws.send (msg); 26 | }); 27 | }); 28 | } 29 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ext.ux.WebSocket", 3 | "version": "0.0.0", 4 | "description": "Ext.ux.WebSocket is an extension to manage HTML5 WebSocket with ExtJS and SenchaTouch", 5 | "main": "WebSocket.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/wilk/ExtJS-WebSocket.git" 12 | }, 13 | "keywords": [ 14 | "extjs", 15 | "senchatouch", 16 | "websocket", 17 | "html5" 18 | ], 19 | "author": "Vincenzo (Wilk)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/wilk/ExtJS-WebSocket/issues" 23 | }, 24 | "homepage": "https://github.com/wilk/ExtJS-WebSocket", 25 | "devDependencies": { 26 | "grunt-contrib-jshint": "~0.8.0", 27 | "load-grunt-tasks": "~0.2.1", 28 | "grunt": "~0.4.2", 29 | "grunt-contrib-uglify": "~0.3.0", 30 | "ws": "~0.4.31" 31 | } 32 | } 33 | --------------------------------------------------------------------------------