diff --git a/.gitignore b/.gitignore index 1c0ee95..73aec89 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ data dist/test TODO.txt node_modules +doc/public diff --git a/browsertests/index.html b/browsertests/index.html index 8642092..409cfc5 100644 --- a/browsertests/index.html +++ b/browsertests/index.html @@ -15,6 +15,8 @@ + + diff --git a/browsertests/integration/integration.js b/browsertests/integration/integration.js index b97bce2..4cd86bb 100644 --- a/browsertests/integration/integration.js +++ b/browsertests/integration/integration.js @@ -17,10 +17,12 @@ $(document).ready(function(){ // the client is notified when it is connected to the server. var onconnect = function(frame) { debug("connected to Stomp"); - client.subscribe(destination, {receipt: 1234}, function(frame) { - client.unsubscribe(destination); - client.disconnect(); - }); + client.subscribe(destination, + function(frame) { + client.unsubscribe(destination); + client.disconnect(); + }, + { receipt: 1234 }); }; client.onerror = function(frame) { debug("connected to Stomp"); diff --git a/browsertests/unit/parse_connect.js b/browsertests/unit/parse_connect.js new file mode 100644 index 0000000..830a926 --- /dev/null +++ b/browsertests/unit/parse_connect.js @@ -0,0 +1,76 @@ +(function() { + module("Parse connect method arguments", { + + setup: function() { + // prepare something for all following tests + myConnectCallback = function() { + // called back when the client is connected to STOMP broker + }; + + myErrorCallback = function() { + // called back if the client can not connect to STOMP broker + }; + + client = Stomp.client(TEST.url); + + checkArgs = function(args, expectedHeaders, expectedConnectCallback, expectedErrorCallback) { + var headers = args[0]; + var connectCallback = args[1]; + var errorCallback = args[2]; + + deepEqual(headers, expectedHeaders); + strictEqual(connectCallback, expectedConnectCallback); + strictEqual(errorCallback, expectedErrorCallback); + } + } + }); + + test("connect(login, passcode, connectCallback)", function() { + checkArgs( + client._parseConnect("jmesnil", "wombats", myConnectCallback), + + {login: 'jmesnil', passcode: 'wombats'}, + myConnectCallback, + undefined); + }); + + test("connect(login, passcode, connectCallback, errorCallback)", function() { + checkArgs( + client._parseConnect("jmesnil", "wombats", myConnectCallback, myErrorCallback), + + {login: 'jmesnil', passcode: 'wombats'}, + myConnectCallback, + myErrorCallback); + }); + + test("connect(login, passcode, connectCallback, errorCallback, vhost)", function() { + checkArgs( + client._parseConnect("jmesnil", "wombats", myConnectCallback, myErrorCallback, "myvhost"), + + {login: 'jmesnil', passcode: 'wombats', vhost: 'myvhost'}, + myConnectCallback, + myErrorCallback); + }); + + test("connect(headers, connectCallback)", function() { + var headers = {login: 'jmesnil', passcode: 'wombats', vhost: 'myvhost'}; + + checkArgs( + client._parseConnect(headers, myConnectCallback), + + headers, + myConnectCallback, + undefined); + }); + + test("connect(headers, connectCallback, errorCallback)", function() { + var headers = {login: 'jmesnil', passcode: 'wombats', vhost: 'myvhost'}; + + checkArgs( + client._parseConnect(headers, myConnectCallback, myErrorCallback), + + headers, + myConnectCallback, + myErrorCallback); + }); +})(); diff --git a/dist/stomp.js b/dist/stomp.js index 854e13f..0ba3b08 100644 --- a/dist/stomp.js +++ b/dist/stomp.js @@ -9,7 +9,8 @@ (function() { var Byte, Client, Frame, Stomp, - __hasProp = {}.hasOwnProperty; + __hasProp = {}.hasOwnProperty, + __slice = [].slice; Byte = { LF: '\x0A', @@ -180,9 +181,36 @@ } }; - Client.prototype.connect = function(login, passcode, connectCallback, errorCallback, vhost) { - var _this = this; - this.connectCallback = connectCallback; + Client.prototype._parseConnect = function() { + var args, connectCallback, errorCallback, headers; + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + headers = {}; + switch (args.length) { + case 2: + headers = args[0], connectCallback = args[1]; + break; + case 3: + if (args[1] instanceof Function) { + headers = args[0], connectCallback = args[1], errorCallback = args[2]; + } else { + headers.login = args[0], headers.passcode = args[1], connectCallback = args[2]; + } + break; + case 4: + headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3]; + break; + default: + headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.vhost = args[4]; + } + return [headers, connectCallback, errorCallback]; + }; + + Client.prototype.connect = function() { + var args, errorCallback, headers, out, + _this = this; + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + out = this._parseConnect.apply(this, args); + headers = out[0], this.connectCallback = out[1], errorCallback = out[2]; if (typeof this.debug === "function") { this.debug("Opening Web Socket..."); } @@ -250,23 +278,11 @@ return typeof errorCallback === "function" ? errorCallback(msg) : void 0; }; return this.ws.onopen = function() { - var headers; if (typeof _this.debug === "function") { _this.debug('Web Socket Opened...'); } - headers = { - "accept-version": Stomp.VERSIONS.supportedVersions(), - "heart-beat": [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',') - }; - if (vhost) { - headers.host = vhost; - } - if (login) { - headers.login = login; - } - if (passcode) { - headers.passcode = passcode; - } + headers["accept-version"] = Stomp.VERSIONS.supportedVersions(); + headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(','); return _this._transmit("CONNECT", headers); }; }; diff --git a/dist/stomp.min.js b/dist/stomp.min.js index b533411..00f31b3 100644 --- a/dist/stomp.min.js +++ b/dist/stomp.min.js @@ -5,4 +5,4 @@ Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/) Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com) */ -!function(){var t,e,n,i,o={}.hasOwnProperty;t={LF:"\n",NULL:"\0"};n=function(){function e(t,e,n){this.command=t;this.headers=e!=null?e:{};this.body=n!=null?n:""}e.prototype.toString=function(){var e,n,i,r;e=[this.command];r=this.headers;for(n in r){if(!o.call(r,n))continue;i=r[n];e.push(""+n+":"+i)}if(this.body){e.push("content-length:"+(""+this.body).length)}e.push(t.LF+this.body);return e.join(t.LF)};e._unmarshallSingle=function(n){var i,o,r,s,u,a,c,f,d,h,p,l,g,b,w,y,v;s=n.search(RegExp(""+t.LF+t.LF));u=n.substring(0,s).split(t.LF);r=u.shift();a={};l=function(t){return t.replace(/^\s+|\s+$/g,"")};y=u.reverse();for(g=0,w=y.length;gv;c=p<=v?++b:--b){o=n.charAt(c);if(o===t.NULL){break}i+=o}}return new e(r,a,i)};e.unmarshall=function(n){var i;return function(){var o,r,s,u;s=n.split(RegExp(""+t.NULL+t.LF+"*"));u=[];for(o=0,r=s.length;o0){u.push(e._unmarshallSingle(i))}}return u}()};e.marshall=function(n,i,o){var r;r=new e(n,i,o);return r.toString()+t.NULL};return e}();e=function(){function e(t){this.ws=t;this.ws.binaryType="arraybuffer";this.counter=0;this.connected=false;this.heartbeat={outgoing:1e4,incoming:1e4};this.maxWebSocketFrameSize=16*1024;this.subscriptions={}}e.prototype.debug=function(t){var e;return typeof window!=="undefined"&&window!==null?(e=window.console)!=null?e.log(t):void 0:void 0};e.prototype._transmit=function(t,e,i){var o;o=n.marshall(t,e,i);if(typeof this.debug==="function"){this.debug(">>> "+o)}while(true){if(o.length>this.maxWebSocketFrameSize){this.ws.send(o.substring(0,this.maxWebSocketFrameSize));o=o.substring(this.maxWebSocketFrameSize);if(typeof this.debug==="function"){this.debug("remaining = "+o.length)}}else{return this.ws.send(o)}}};e.prototype._setupHeartbeat=function(e){var n,o,r,s,u,a,c=this;if((u=e.version)!==i.VERSIONS.V1_1&&u!==i.VERSIONS.V1_2){return}a=function(){var t,n,i,o;i=e["heart-beat"].split(",");o=[];for(t=0,n=i.length;t>> PING"):void 0},r):void 0}if(!(this.heartbeat.incoming===0||o===0)){r=Math.max(this.heartbeat.incoming,o);if(typeof this.debug==="function"){this.debug("check PONG every "+r+"ms")}return this.ponger=typeof window!=="undefined"&&window!==null?window.setInterval(function(){var t;t=Date.now()-c.serverActivity;if(t>r*2){if(typeof c.debug==="function"){c.debug("did not receive server activity for the last "+t+"ms")}return c.ws.close()}},r):void 0}};e.prototype.connect=function(e,o,r,s,u){var a=this;this.connectCallback=r;if(typeof this.debug==="function"){this.debug("Opening Web Socket...")}this.ws.onmessage=function(e){var i,o,r,u,c,f,d,h,p;r=typeof ArrayBuffer!=="undefined"&&e.data instanceof ArrayBuffer?(i=new Uint8Array(e.data),typeof a.debug==="function"?a.debug("--- got data length: "+i.length):void 0,function(){var t,e,n;n=[];for(t=0,e=i.length;tw;c=l<=w?++b:--b){o=n.charAt(c);if(o===t.NULL){break}i+=o}}return new e(r,a,i)};e.unmarshall=function(n){var i;return function(){var o,r,s,u;s=n.split(RegExp(""+t.NULL+t.LF+"*"));u=[];for(o=0,r=s.length;o0){u.push(e._unmarshallSingle(i))}}return u}()};e.marshall=function(n,i,o){var r;r=new e(n,i,o);return r.toString()+t.NULL};return e}();e=function(){function e(t){this.ws=t;this.ws.binaryType="arraybuffer";this.counter=0;this.connected=false;this.heartbeat={outgoing:1e4,incoming:1e4};this.maxWebSocketFrameSize=16*1024;this.subscriptions={}}e.prototype.debug=function(t){var e;return typeof window!=="undefined"&&window!==null?(e=window.console)!=null?e.log(t):void 0:void 0};e.prototype._transmit=function(t,e,i){var o;o=n.marshall(t,e,i);if(typeof this.debug==="function"){this.debug(">>> "+o)}while(true){if(o.length>this.maxWebSocketFrameSize){this.ws.send(o.substring(0,this.maxWebSocketFrameSize));o=o.substring(this.maxWebSocketFrameSize);if(typeof this.debug==="function"){this.debug("remaining = "+o.length)}}else{return this.ws.send(o)}}};e.prototype._setupHeartbeat=function(e){var n,o,r,s,u,a,c=this;if((u=e.version)!==i.VERSIONS.V1_1&&u!==i.VERSIONS.V1_2){return}a=function(){var t,n,i,o;i=e["heart-beat"].split(",");o=[];for(t=0,n=i.length;t>> PING"):void 0},r):void 0}if(!(this.heartbeat.incoming===0||o===0)){r=Math.max(this.heartbeat.incoming,o);if(typeof this.debug==="function"){this.debug("check PONG every "+r+"ms")}return this.ponger=typeof window!=="undefined"&&window!==null?window.setInterval(function(){var t;t=Date.now()-c.serverActivity;if(t>r*2){if(typeof c.debug==="function"){c.debug("did not receive server activity for the last "+t+"ms")}return c.ws.close()}},r):void 0}};e.prototype._parseConnect=function(){var t,e,n,i;t=1<=arguments.length?r.call(arguments,0):[];i={};switch(t.length){case 2:i=t[0],e=t[1];break;case 3:if(t[1]instanceof Function){i=t[0],e=t[1],n=t[2]}else{i.login=t[0],i.passcode=t[1],e=t[2]}break;case 4:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3];break;default:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3],i.vhost=t[4]}return[i,e,n]};e.prototype.connect=function(){var e,o,s,u,a=this;e=1<=arguments.length?r.call(arguments,0):[];u=this._parseConnect.apply(this,e);s=u[0],this.connectCallback=u[1],o=u[2];if(typeof this.debug==="function"){this.debug("Opening Web Socket...")}this.ws.onmessage=function(e){var i,r,s,u,c,f,d,h,l;s=typeof ArrayBuffer!=="undefined"&&e.data instanceof ArrayBuffer?(i=new Uint8Array(e.data),typeof a.debug==="function"?a.debug("--- got data length: "+i.length):void 0,function(){var t,e,n;n=[];for(t=0,e=i.length;tConnection to the server

Behind the scene, the client will open a connection using a WebSocket and send a CONNECT frame.

The connection is done asynchronously: you have no guarantee to be effectively connected when - the call to connect returns. To be notified of the connection, you can pass a + the call to connect returns. To be notified of the connection, you need to pass a connect_callback function to the connect() method:


-  client.connect(login, passcode, connect_callback);
-
-  connect_callback = function() {
+  var connect_callback = function() {
     // called back after the client is connected and authenticated to the STOMP server
   };
 
-

But what happens if the connection fails? the connect() method accepts an optional error_callback argument which will be called if the client is not able to connect to the server. @@ -223,21 +220,43 @@

Connection to the server

ERROR frame:


-  client.connect(login, passcode, connect_callback, error_callback);
-
-  error_callback = function(error) {
+  var error_callback = function(error) {
     // display the error's message header:
     alert(error.headers.message);
   };
 
-v

Finally, if the STOMP broker requires a host, pass it as the last argument of the connect() method: +

The connect() method accepts different number of arguments to provide a simple API to use in most cases:


-  var host = "...";
-  client.connect(login, passcode, connect_callback, error_callback, host);
+  client.connect(login, passcode, connectCallback);
+  client.connect(login, passcode, connectCallback, errorCallback);
+  client.connect(login, passcode, connectCallback, errorCallback, host);
 
+

where login, passcode are strings and connectCallback and errorCallback are functions + (some brokers also require to pass a host String). + +

The connect() method also accepts two other variants if you need to pass additional headers: + +


+  client.connect(headers, connectCallback);
+  client.connect(headers, connectCallback, errorCallback);
+
+ +

where header is a map and connectCallback and errorCallback are functions. +

Please note that if you use these forms, you must add the login, passcode (and eventually host) + headers yourself: + +


+    var headers = {
+      login: 'mylogin',
+      passcode: 'mypasscode',
+      // additional header
+      'client-id': 'my-client-id'
+    };
+    client.connect(headers, connectCallback);
+  

To disconnect a client from the server, you can call its disconnect() method. The disconnection is asynchronous: to be notified when the disconnection is effective, diff --git a/src/stomp.coffee b/src/stomp.coffee index 7f0bf41..bd23ae6 100644 --- a/src/stomp.coffee +++ b/src/stomp.coffee @@ -174,12 +174,40 @@ class Client @ws.close() , ttl) + # parse the arguments number and type to find the headers, connectCallback and + # (eventually undefined) errorCallback + _parseConnect: (args...) -> + headers = {} + switch args.length + when 2 + [headers, connectCallback] = args + when 3 + if args[1] instanceof Function + [headers, connectCallback, errorCallback] = args + else + [headers.login, headers.passcode, connectCallback] = args + when 4 + [headers.login, headers.passcode, connectCallback, errorCallback] = args + else + [headers.login, headers.passcode, connectCallback, errorCallback, headers.vhost] = args + + [headers, connectCallback, errorCallback] + # [CONNECT Frame](http://stomp.github.com/stomp-specification-1.1.html#CONNECT_or_STOMP_Frame) - connect: (login, - passcode, - @connectCallback, - errorCallback, - vhost) -> + # + # The `connect` method accepts different number of arguments and types: + # + # * `connect(headers, connectCallback)` + # * `connect(headers, connectCallback, errorCallback)` + # * `connect(login, passcode, connectCallback)` + # * `connect(login, passcode, connectCallback, errorCallback)` + # * `connect(login, passcode, connectCallback, errorCallback, vhost)` + # + # The errorCallback is optional and the 2 first forms allow to pass other + # headers in addition to `client`, `passcode` and `vhost`. + connect: (args...) -> + out = @_parseConnect(args...) + [headers, @connectCallback, errorCallback] = out @debug? "Opening Web Socket..." @ws.onmessage = (evt) => data = if typeof(ArrayBuffer) != 'undefined' and evt.data instanceof ArrayBuffer @@ -244,13 +272,8 @@ class Client errorCallback?(msg) @ws.onopen = => @debug?('Web Socket Opened...') - headers = { - "accept-version": Stomp.VERSIONS.supportedVersions() - "heart-beat": [@heartbeat.outgoing, @heartbeat.incoming].join(',') - } - headers.host = vhost if vhost - headers.login = login if login - headers.passcode = passcode if passcode + headers["accept-version"] = Stomp.VERSIONS.supportedVersions() + headers["heart-beat"] = [@heartbeat.outgoing, @heartbeat.incoming].join(',') @_transmit "CONNECT", headers # [DISCONNECT Frame](http://stomp.github.com/stomp-specification-1.1.html#DISCONNECT)