/lib/stomp.js

https://github.com/jmesnil/stomp-websocket · JavaScript · 501 lines · 458 code · 36 blank · 7 comment · 84 complexity · 808dec911f89464647fda5335c3861de MD5 · raw file

  1. // Generated by CoffeeScript 1.7.1
  2. /*
  3. Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0
  4. Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/)
  5. Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com)
  6. */
  7. (function() {
  8. var Byte, Client, Frame, Stomp,
  9. __hasProp = {}.hasOwnProperty,
  10. __slice = [].slice;
  11. Byte = {
  12. LF: '\x0A',
  13. NULL: '\x00'
  14. };
  15. Frame = (function() {
  16. var unmarshallSingle;
  17. function Frame(command, headers, body) {
  18. this.command = command;
  19. this.headers = headers != null ? headers : {};
  20. this.body = body != null ? body : '';
  21. }
  22. Frame.prototype.toString = function() {
  23. var lines, name, skipContentLength, value, _ref;
  24. lines = [this.command];
  25. skipContentLength = this.headers['content-length'] === false ? true : false;
  26. if (skipContentLength) {
  27. delete this.headers['content-length'];
  28. }
  29. _ref = this.headers;
  30. for (name in _ref) {
  31. if (!__hasProp.call(_ref, name)) continue;
  32. value = _ref[name];
  33. lines.push("" + name + ":" + value);
  34. }
  35. if (this.body && !skipContentLength) {
  36. lines.push("content-length:" + (Frame.sizeOfUTF8(this.body)));
  37. }
  38. lines.push(Byte.LF + this.body);
  39. return lines.join(Byte.LF);
  40. };
  41. Frame.sizeOfUTF8 = function(s) {
  42. if (s) {
  43. return encodeURI(s).match(/%..|./g).length;
  44. } else {
  45. return 0;
  46. }
  47. };
  48. unmarshallSingle = function(data) {
  49. var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref, _ref1;
  50. divider = data.search(RegExp("" + Byte.LF + Byte.LF));
  51. headerLines = data.substring(0, divider).split(Byte.LF);
  52. command = headerLines.shift();
  53. headers = {};
  54. trim = function(str) {
  55. return str.replace(/^\s+|\s+$/g, '');
  56. };
  57. _ref = headerLines.reverse();
  58. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  59. line = _ref[_i];
  60. idx = line.indexOf(':');
  61. headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
  62. }
  63. body = '';
  64. start = divider + 2;
  65. if (headers['content-length']) {
  66. len = parseInt(headers['content-length']);
  67. body = ('' + data).substring(start, start + len);
  68. } else {
  69. chr = null;
  70. for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) {
  71. chr = data.charAt(i);
  72. if (chr === Byte.NULL) {
  73. break;
  74. }
  75. body += chr;
  76. }
  77. }
  78. return new Frame(command, headers, body);
  79. };
  80. Frame.unmarshall = function(datas) {
  81. var frame, frames, last_frame, r;
  82. frames = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*"));
  83. r = {
  84. frames: [],
  85. partial: ''
  86. };
  87. r.frames = (function() {
  88. var _i, _len, _ref, _results;
  89. _ref = frames.slice(0, -1);
  90. _results = [];
  91. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  92. frame = _ref[_i];
  93. _results.push(unmarshallSingle(frame));
  94. }
  95. return _results;
  96. })();
  97. last_frame = frames.slice(-1)[0];
  98. if (last_frame === Byte.LF || (last_frame.search(RegExp("" + Byte.NULL + Byte.LF + "*$"))) !== -1) {
  99. r.frames.push(unmarshallSingle(last_frame));
  100. } else {
  101. r.partial = last_frame;
  102. }
  103. return r;
  104. };
  105. Frame.marshall = function(command, headers, body) {
  106. var frame;
  107. frame = new Frame(command, headers, body);
  108. return frame.toString() + Byte.NULL;
  109. };
  110. return Frame;
  111. })();
  112. Client = (function() {
  113. var now;
  114. function Client(ws) {
  115. this.ws = ws;
  116. this.ws.binaryType = "arraybuffer";
  117. this.counter = 0;
  118. this.connected = false;
  119. this.heartbeat = {
  120. outgoing: 10000,
  121. incoming: 10000
  122. };
  123. this.maxWebSocketFrameSize = 16 * 1024;
  124. this.subscriptions = {};
  125. this.partialData = '';
  126. }
  127. Client.prototype.debug = function(message) {
  128. var _ref;
  129. return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0;
  130. };
  131. now = function() {
  132. if (Date.now) {
  133. return Date.now();
  134. } else {
  135. return new Date().valueOf;
  136. }
  137. };
  138. Client.prototype._transmit = function(command, headers, body) {
  139. var out;
  140. out = Frame.marshall(command, headers, body);
  141. if (typeof this.debug === "function") {
  142. this.debug(">>> " + out);
  143. }
  144. while (true) {
  145. if (out.length > this.maxWebSocketFrameSize) {
  146. this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
  147. out = out.substring(this.maxWebSocketFrameSize);
  148. if (typeof this.debug === "function") {
  149. this.debug("remaining = " + out.length);
  150. }
  151. } else {
  152. return this.ws.send(out);
  153. }
  154. }
  155. };
  156. Client.prototype._setupHeartbeat = function(headers) {
  157. var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
  158. if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) {
  159. return;
  160. }
  161. _ref1 = (function() {
  162. var _i, _len, _ref1, _results;
  163. _ref1 = headers['heart-beat'].split(",");
  164. _results = [];
  165. for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
  166. v = _ref1[_i];
  167. _results.push(parseInt(v));
  168. }
  169. return _results;
  170. })(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1];
  171. if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
  172. ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
  173. if (typeof this.debug === "function") {
  174. this.debug("send PING every " + ttl + "ms");
  175. }
  176. this.pinger = Stomp.setInterval(ttl, (function(_this) {
  177. return function() {
  178. _this.ws.send(Byte.LF);
  179. return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0;
  180. };
  181. })(this));
  182. }
  183. if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
  184. ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
  185. if (typeof this.debug === "function") {
  186. this.debug("check PONG every " + ttl + "ms");
  187. }
  188. return this.ponger = Stomp.setInterval(ttl, (function(_this) {
  189. return function() {
  190. var delta;
  191. delta = now() - _this.serverActivity;
  192. if (delta > ttl * 2) {
  193. if (typeof _this.debug === "function") {
  194. _this.debug("did not receive server activity for the last " + delta + "ms");
  195. }
  196. return _this.ws.close();
  197. }
  198. };
  199. })(this));
  200. }
  201. };
  202. Client.prototype._parseConnect = function() {
  203. var args, connectCallback, errorCallback, headers;
  204. args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  205. headers = {};
  206. switch (args.length) {
  207. case 2:
  208. headers = args[0], connectCallback = args[1];
  209. break;
  210. case 3:
  211. if (args[1] instanceof Function) {
  212. headers = args[0], connectCallback = args[1], errorCallback = args[2];
  213. } else {
  214. headers.login = args[0], headers.passcode = args[1], connectCallback = args[2];
  215. }
  216. break;
  217. case 4:
  218. headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3];
  219. break;
  220. default:
  221. headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4];
  222. }
  223. return [headers, connectCallback, errorCallback];
  224. };
  225. Client.prototype.connect = function() {
  226. var args, errorCallback, headers, out;
  227. args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  228. out = this._parseConnect.apply(this, args);
  229. headers = out[0], this.connectCallback = out[1], errorCallback = out[2];
  230. if (typeof this.debug === "function") {
  231. this.debug("Opening Web Socket...");
  232. }
  233. this.ws.onmessage = (function(_this) {
  234. return function(evt) {
  235. var arr, c, client, data, frame, messageID, onreceive, subscription, unmarshalledData, _i, _len, _ref, _results;
  236. data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() {
  237. var _i, _len, _results;
  238. _results = [];
  239. for (_i = 0, _len = arr.length; _i < _len; _i++) {
  240. c = arr[_i];
  241. _results.push(String.fromCharCode(c));
  242. }
  243. return _results;
  244. })()).join('')) : evt.data;
  245. _this.serverActivity = now();
  246. if (data === Byte.LF) {
  247. if (typeof _this.debug === "function") {
  248. _this.debug("<<< PONG");
  249. }
  250. return;
  251. }
  252. if (typeof _this.debug === "function") {
  253. _this.debug("<<< " + data);
  254. }
  255. unmarshalledData = Frame.unmarshall(_this.partialData + data);
  256. _this.partialData = unmarshalledData.partial;
  257. _ref = unmarshalledData.frames;
  258. _results = [];
  259. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  260. frame = _ref[_i];
  261. switch (frame.command) {
  262. case "CONNECTED":
  263. if (typeof _this.debug === "function") {
  264. _this.debug("connected to server " + frame.headers.server);
  265. }
  266. _this.connected = true;
  267. _this._setupHeartbeat(frame.headers);
  268. _results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0);
  269. break;
  270. case "MESSAGE":
  271. subscription = frame.headers.subscription;
  272. onreceive = _this.subscriptions[subscription] || _this.onreceive;
  273. if (onreceive) {
  274. client = _this;
  275. messageID = frame.headers["message-id"];
  276. frame.ack = function(headers) {
  277. if (headers == null) {
  278. headers = {};
  279. }
  280. return client.ack(messageID, subscription, headers);
  281. };
  282. frame.nack = function(headers) {
  283. if (headers == null) {
  284. headers = {};
  285. }
  286. return client.nack(messageID, subscription, headers);
  287. };
  288. _results.push(onreceive(frame));
  289. } else {
  290. _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0);
  291. }
  292. break;
  293. case "RECEIPT":
  294. _results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0);
  295. break;
  296. case "ERROR":
  297. _results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0);
  298. break;
  299. default:
  300. _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0);
  301. }
  302. }
  303. return _results;
  304. };
  305. })(this);
  306. this.ws.onclose = (function(_this) {
  307. return function() {
  308. var msg;
  309. msg = "Whoops! Lost connection to " + _this.ws.url;
  310. if (typeof _this.debug === "function") {
  311. _this.debug(msg);
  312. }
  313. _this._cleanUp();
  314. return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
  315. };
  316. })(this);
  317. return this.ws.onopen = (function(_this) {
  318. return function() {
  319. if (typeof _this.debug === "function") {
  320. _this.debug('Web Socket Opened...');
  321. }
  322. headers["accept-version"] = Stomp.VERSIONS.supportedVersions();
  323. headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
  324. return _this._transmit("CONNECT", headers);
  325. };
  326. })(this);
  327. };
  328. Client.prototype.disconnect = function(disconnectCallback, headers) {
  329. if (headers == null) {
  330. headers = {};
  331. }
  332. this._transmit("DISCONNECT", headers);
  333. this.ws.onclose = null;
  334. this.ws.close();
  335. this._cleanUp();
  336. return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
  337. };
  338. Client.prototype._cleanUp = function() {
  339. this.connected = false;
  340. if (this.pinger) {
  341. Stomp.clearInterval(this.pinger);
  342. }
  343. if (this.ponger) {
  344. return Stomp.clearInterval(this.ponger);
  345. }
  346. };
  347. Client.prototype.send = function(destination, headers, body) {
  348. if (headers == null) {
  349. headers = {};
  350. }
  351. if (body == null) {
  352. body = '';
  353. }
  354. headers.destination = destination;
  355. return this._transmit("SEND", headers, body);
  356. };
  357. Client.prototype.subscribe = function(destination, callback, headers) {
  358. var client;
  359. if (headers == null) {
  360. headers = {};
  361. }
  362. if (!headers.id) {
  363. headers.id = "sub-" + this.counter++;
  364. }
  365. headers.destination = destination;
  366. this.subscriptions[headers.id] = callback;
  367. this._transmit("SUBSCRIBE", headers);
  368. client = this;
  369. return {
  370. id: headers.id,
  371. unsubscribe: function() {
  372. return client.unsubscribe(headers.id);
  373. }
  374. };
  375. };
  376. Client.prototype.unsubscribe = function(id) {
  377. delete this.subscriptions[id];
  378. return this._transmit("UNSUBSCRIBE", {
  379. id: id
  380. });
  381. };
  382. Client.prototype.begin = function(transaction) {
  383. var client, txid;
  384. txid = transaction || "tx-" + this.counter++;
  385. this._transmit("BEGIN", {
  386. transaction: txid
  387. });
  388. client = this;
  389. return {
  390. id: txid,
  391. commit: function() {
  392. return client.commit(txid);
  393. },
  394. abort: function() {
  395. return client.abort(txid);
  396. }
  397. };
  398. };
  399. Client.prototype.commit = function(transaction) {
  400. return this._transmit("COMMIT", {
  401. transaction: transaction
  402. });
  403. };
  404. Client.prototype.abort = function(transaction) {
  405. return this._transmit("ABORT", {
  406. transaction: transaction
  407. });
  408. };
  409. Client.prototype.ack = function(messageID, subscription, headers) {
  410. if (headers == null) {
  411. headers = {};
  412. }
  413. headers["message-id"] = messageID;
  414. headers.subscription = subscription;
  415. return this._transmit("ACK", headers);
  416. };
  417. Client.prototype.nack = function(messageID, subscription, headers) {
  418. if (headers == null) {
  419. headers = {};
  420. }
  421. headers["message-id"] = messageID;
  422. headers.subscription = subscription;
  423. return this._transmit("NACK", headers);
  424. };
  425. return Client;
  426. })();
  427. Stomp = {
  428. VERSIONS: {
  429. V1_0: '1.0',
  430. V1_1: '1.1',
  431. V1_2: '1.2',
  432. supportedVersions: function() {
  433. return '1.1,1.0';
  434. }
  435. },
  436. client: function(url, protocols) {
  437. var klass, ws;
  438. if (protocols == null) {
  439. protocols = ['v10.stomp', 'v11.stomp'];
  440. }
  441. klass = Stomp.WebSocketClass || WebSocket;
  442. ws = new klass(url, protocols);
  443. return new Client(ws);
  444. },
  445. over: function(ws) {
  446. return new Client(ws);
  447. },
  448. Frame: Frame
  449. };
  450. if (typeof exports !== "undefined" && exports !== null) {
  451. exports.Stomp = Stomp;
  452. }
  453. if (typeof window !== "undefined" && window !== null) {
  454. Stomp.setInterval = function(interval, f) {
  455. return window.setInterval(f, interval);
  456. };
  457. Stomp.clearInterval = function(id) {
  458. return window.clearInterval(id);
  459. };
  460. window.Stomp = Stomp;
  461. } else if (!exports) {
  462. self.Stomp = Stomp;
  463. }
  464. }).call(this);