PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/autobahn/autobahn.js

http://github.com/tavendo/AutobahnJS
JavaScript | 1204 lines | 853 code | 233 blank | 118 comment | 205 complexity | b2216c573f890eed803556fe83004199 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0
  1. /** @license MIT License (c) 2011-2013 Copyright Tavendo GmbH. */
  2. /**
  3. * AutobahnJS - http://autobahn.ws
  4. *
  5. * A lightweight implementation of
  6. *
  7. * WAMP (The WebSocket Application Messaging Protocol) - http://wamp.ws
  8. *
  9. * Provides asynchronous RPC/PubSub over WebSocket.
  10. *
  11. * Copyright 2011-2013 Tavendo GmbH. Licensed under the MIT License.
  12. * See license text at http://www.opensource.org/licenses/mit-license.php
  13. */
  14. /*global console: false, MozWebSocket: false, when: false, CryptoJS: false */
  15. "use strict";
  16. /** @define {string} */
  17. var AUTOBAHNJS_VERSION = '?.?.?';
  18. /** @define {boolean} */
  19. var AUTOBAHNJS_DEBUG = true;
  20. var ab = window.ab = {};
  21. ab._version = AUTOBAHNJS_VERSION;
  22. /**
  23. * Fallbacks for browsers lacking
  24. *
  25. * Array.prototype.indexOf
  26. * Array.prototype.forEach
  27. *
  28. * most notably MSIE8.
  29. *
  30. * Source:
  31. * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
  32. * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
  33. */
  34. (function () {
  35. if (!Array.prototype.indexOf) {
  36. Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
  37. "use strict";
  38. if (this === null) {
  39. throw new TypeError();
  40. }
  41. var t = new Object(this);
  42. var len = t.length >>> 0;
  43. if (len === 0) {
  44. return -1;
  45. }
  46. var n = 0;
  47. if (arguments.length > 0) {
  48. n = Number(arguments[1]);
  49. if (n !== n) { // shortcut for verifying if it's NaN
  50. n = 0;
  51. } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
  52. n = (n > 0 || -1) * Math.floor(Math.abs(n));
  53. }
  54. }
  55. if (n >= len) {
  56. return -1;
  57. }
  58. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  59. for (; k < len; k++) {
  60. if (k in t && t[k] === searchElement) {
  61. return k;
  62. }
  63. }
  64. return -1;
  65. };
  66. }
  67. if (!Array.prototype.forEach) {
  68. Array.prototype.forEach = function (callback, thisArg) {
  69. var T, k;
  70. if (this === null) {
  71. throw new TypeError(" this is null or not defined");
  72. }
  73. // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
  74. var O = new Object(this);
  75. // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
  76. // 3. Let len be ToUint32(lenValue).
  77. var len = O.length >>> 0; // Hack to convert O.length to a UInt32
  78. // 4. If IsCallable(callback) is false, throw a TypeError exception.
  79. // See: http://es5.github.com/#x9.11
  80. if ({}.toString.call(callback) !== "[object Function]") {
  81. throw new TypeError(callback + " is not a function");
  82. }
  83. // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
  84. if (thisArg) {
  85. T = thisArg;
  86. }
  87. // 6. Let k be 0
  88. k = 0;
  89. // 7. Repeat, while k < len
  90. while (k < len) {
  91. var kValue;
  92. // a. Let Pk be ToString(k).
  93. // This is implicit for LHS operands of the in operator
  94. // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
  95. // This step can be combined with c
  96. // c. If kPresent is true, then
  97. if (k in O) {
  98. // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
  99. kValue = O[k];
  100. // ii. Call the Call internal method of callback with T as the this value and
  101. // argument list containing kValue, k, and O.
  102. callback.call(T, kValue, k, O);
  103. }
  104. // d. Increase k by 1.
  105. k++;
  106. }
  107. // 8. return undefined
  108. };
  109. }
  110. })();
  111. // Helper to slice out browser / version from userAgent
  112. ab._sliceUserAgent = function (str, delim, delim2) {
  113. var ver = [];
  114. var ua = navigator.userAgent;
  115. var i = ua.indexOf(str);
  116. var j = ua.indexOf(delim, i);
  117. if (j < 0) {
  118. j = ua.length;
  119. }
  120. var agent = ua.slice(i, j).split(delim2);
  121. var v = agent[1].split('.');
  122. for (var k = 0; k < v.length; ++k) {
  123. ver.push(parseInt(v[k], 10));
  124. }
  125. return {name: agent[0], version: ver};
  126. };
  127. /**
  128. * Detect browser and browser version.
  129. */
  130. ab.getBrowser = function () {
  131. var ua = navigator.userAgent;
  132. if (ua.indexOf("Chrome") > -1) {
  133. return ab._sliceUserAgent("Chrome", " ", "/");
  134. } else if (ua.indexOf("Safari") > -1) {
  135. return ab._sliceUserAgent("Safari", " ", "/");
  136. } else if (ua.indexOf("Firefox") > -1) {
  137. return ab._sliceUserAgent("Firefox", " ", "/");
  138. } else if (ua.indexOf("MSIE") > -1) {
  139. return ab._sliceUserAgent("MSIE", ";", " ");
  140. } else {
  141. return null;
  142. }
  143. };
  144. ab.getServerUrl = function (wsPath, fallbackUrl) {
  145. if (window.location.protocol === "file:") {
  146. if (fallbackUrl) {
  147. return fallbackUrl;
  148. } else {
  149. return "ws://127.0.0.1/ws";
  150. }
  151. } else {
  152. var scheme = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
  153. var port = window.location.port !== "" ? ':' + window.location.port : '';
  154. var path = wsPath ? wsPath : 'ws';
  155. return scheme + window.location.hostname + port + "/" + path;
  156. }
  157. };
  158. // Logging message for unsupported browser.
  159. ab.browserNotSupportedMessage = "Browser does not support WebSockets (RFC6455)";
  160. // PBKDF2-base key derivation function for salted WAMP-CRA
  161. ab.deriveKey = function (secret, extra) {
  162. if (extra && extra.salt) {
  163. var salt = extra.salt;
  164. var keylen = extra.keylen || 32;
  165. var iterations = extra.iterations || 10000;
  166. var key = CryptoJS.PBKDF2(secret, salt, { keySize: keylen / 4, iterations: iterations, hasher: CryptoJS.algo.SHA256 });
  167. return key.toString(CryptoJS.enc.Base64);
  168. } else {
  169. return secret;
  170. }
  171. };
  172. ab._idchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  173. ab._idlen = 16;
  174. ab._subprotocol = "wamp";
  175. ab._newid = function () {
  176. var id = "";
  177. for (var i = 0; i < ab._idlen; i += 1) {
  178. id += ab._idchars.charAt(Math.floor(Math.random() * ab._idchars.length));
  179. }
  180. return id;
  181. };
  182. ab._newidFast = function () {
  183. return Math.random().toString(36);
  184. };
  185. ab.log = function () {
  186. //console.log.apply(console, !!arguments.length ? arguments : [this]);
  187. if (arguments.length > 1) {
  188. console.group("Log Item");
  189. for (var i = 0; i < arguments.length; i += 1) {
  190. console.log(arguments[i]);
  191. }
  192. console.groupEnd();
  193. } else {
  194. console.log(arguments[0]);
  195. }
  196. };
  197. ab._debugrpc = false;
  198. ab._debugpubsub = false;
  199. ab._debugws = false;
  200. ab._debugconnect = false;
  201. ab.debug = function (debugWamp, debugWs, debugConnect) {
  202. if ("console" in window) {
  203. ab._debugrpc = debugWamp;
  204. ab._debugpubsub = debugWamp;
  205. ab._debugws = debugWs;
  206. ab._debugconnect = debugConnect;
  207. } else {
  208. throw "browser does not support console object";
  209. }
  210. };
  211. ab.version = function () {
  212. return ab._version;
  213. };
  214. ab.PrefixMap = function () {
  215. var self = this;
  216. self._index = {};
  217. self._rindex = {};
  218. };
  219. ab.PrefixMap.prototype.get = function (prefix) {
  220. var self = this;
  221. return self._index[prefix];
  222. };
  223. ab.PrefixMap.prototype.set = function (prefix, uri) {
  224. var self = this;
  225. self._index[prefix] = uri;
  226. self._rindex[uri] = prefix;
  227. };
  228. ab.PrefixMap.prototype.setDefault = function (uri) {
  229. var self = this;
  230. self._index[""] = uri;
  231. self._rindex[uri] = "";
  232. };
  233. ab.PrefixMap.prototype.remove = function (prefix) {
  234. var self = this;
  235. var uri = self._index[prefix];
  236. if (uri) {
  237. delete self._index[prefix];
  238. delete self._rindex[uri];
  239. }
  240. };
  241. ab.PrefixMap.prototype.resolve = function (curie, pass) {
  242. var self = this;
  243. // skip if not a CURIE
  244. var i = curie.indexOf(":");
  245. if (i >= 0) {
  246. var prefix = curie.substring(0, i);
  247. if (self._index[prefix]) {
  248. return self._index[prefix] + curie.substring(i + 1);
  249. }
  250. }
  251. // either pass-through or null
  252. if (pass === true) {
  253. return curie;
  254. } else {
  255. return null;
  256. }
  257. };
  258. ab.PrefixMap.prototype.shrink = function (uri, pass) {
  259. var self = this;
  260. for (var i = uri.length; i > 0; i -= 1) {
  261. var u = uri.substring(0, i);
  262. var p = self._rindex[u];
  263. if (p) {
  264. return p + ":" + uri.substring(i);
  265. }
  266. }
  267. // either pass-through or null
  268. if (pass === true) {
  269. return uri;
  270. } else {
  271. return null;
  272. }
  273. };
  274. ab._MESSAGE_TYPEID_WELCOME = 0;
  275. ab._MESSAGE_TYPEID_PREFIX = 1;
  276. ab._MESSAGE_TYPEID_CALL = 2;
  277. ab._MESSAGE_TYPEID_CALL_RESULT = 3;
  278. ab._MESSAGE_TYPEID_CALL_ERROR = 4;
  279. ab._MESSAGE_TYPEID_SUBSCRIBE = 5;
  280. ab._MESSAGE_TYPEID_UNSUBSCRIBE = 6;
  281. ab._MESSAGE_TYPEID_PUBLISH = 7;
  282. ab._MESSAGE_TYPEID_EVENT = 8;
  283. ab.CONNECTION_CLOSED = 0;
  284. ab.CONNECTION_LOST = 1;
  285. ab.CONNECTION_RETRIES_EXCEEDED = 2;
  286. ab.CONNECTION_UNREACHABLE = 3;
  287. ab.CONNECTION_UNSUPPORTED = 4;
  288. ab.CONNECTION_UNREACHABLE_SCHEDULED_RECONNECT = 5;
  289. ab.CONNECTION_LOST_SCHEDULED_RECONNECT = 6;
  290. ab.Deferred = when.defer;
  291. //ab.Deferred = jQuery.Deferred;
  292. ab._construct = function (url, protocols) {
  293. if ("WebSocket" in window) {
  294. // Chrome, MSIE, newer Firefox
  295. if (protocols) {
  296. return new WebSocket(url, protocols);
  297. } else {
  298. return new WebSocket(url);
  299. }
  300. } else if ("MozWebSocket" in window) {
  301. // older versions of Firefox prefix the WebSocket object
  302. if (protocols) {
  303. return new MozWebSocket(url, protocols);
  304. } else {
  305. return new MozWebSocket(url);
  306. }
  307. } else {
  308. return null;
  309. }
  310. };
  311. ab.Session = function (wsuri, onopen, onclose, options) {
  312. var self = this;
  313. self._wsuri = wsuri;
  314. self._options = options;
  315. self._websocket_onopen = onopen;
  316. self._websocket_onclose = onclose;
  317. self._websocket = null;
  318. self._websocket_connected = false;
  319. self._session_id = null;
  320. self._wamp_version = null;
  321. self._server = null;
  322. self._calls = {};
  323. self._subscriptions = {};
  324. self._prefixes = new ab.PrefixMap();
  325. self._txcnt = 0;
  326. self._rxcnt = 0;
  327. if (self._options && self._options.skipSubprotocolAnnounce) {
  328. self._websocket = ab._construct(self._wsuri);
  329. } else {
  330. self._websocket = ab._construct(self._wsuri, [ab._subprotocol]);
  331. }
  332. if (!self._websocket) {
  333. if (onclose !== undefined) {
  334. onclose(ab.CONNECTION_UNSUPPORTED);
  335. return;
  336. } else {
  337. throw ab.browserNotSupportedMessage;
  338. }
  339. }
  340. self._websocket.onmessage = function (e)
  341. {
  342. if (ab._debugws) {
  343. self._rxcnt += 1;
  344. console.group("WS Receive");
  345. console.info(self._wsuri + " [" + self._session_id + "]");
  346. console.log(self._rxcnt);
  347. console.log(e.data);
  348. console.groupEnd();
  349. }
  350. var o = JSON.parse(e.data);
  351. if (o[1] in self._calls)
  352. {
  353. if (o[0] === ab._MESSAGE_TYPEID_CALL_RESULT) {
  354. var dr = self._calls[o[1]];
  355. var r = o[2];
  356. if (ab._debugrpc && dr._ab_callobj !== undefined) {
  357. console.group("WAMP Call", dr._ab_callobj[2]);
  358. console.timeEnd(dr._ab_tid);
  359. console.group("Arguments");
  360. for (var i = 3; i < dr._ab_callobj.length; i += 1) {
  361. var arg = dr._ab_callobj[i];
  362. if (arg !== undefined) {
  363. console.log(arg);
  364. } else {
  365. break;
  366. }
  367. }
  368. console.groupEnd();
  369. console.group("Result");
  370. console.log(r);
  371. console.groupEnd();
  372. console.groupEnd();
  373. }
  374. dr.resolve(r);
  375. }
  376. else if (o[0] === ab._MESSAGE_TYPEID_CALL_ERROR) {
  377. var de = self._calls[o[1]];
  378. var uri_ = o[2];
  379. var desc_ = o[3];
  380. var detail_ = o[4];
  381. if (ab._debugrpc && de._ab_callobj !== undefined) {
  382. console.group("WAMP Call", de._ab_callobj[2]);
  383. console.timeEnd(de._ab_tid);
  384. console.group("Arguments");
  385. for (var j = 3; j < de._ab_callobj.length; j += 1) {
  386. var arg2 = de._ab_callobj[j];
  387. if (arg2 !== undefined) {
  388. console.log(arg2);
  389. } else {
  390. break;
  391. }
  392. }
  393. console.groupEnd();
  394. console.group("Error");
  395. console.log(uri_);
  396. console.log(desc_);
  397. if (detail_ !== undefined) {
  398. console.log(detail_);
  399. }
  400. console.groupEnd();
  401. console.groupEnd();
  402. }
  403. if (detail_ !== undefined) {
  404. de.reject({uri: uri_, desc: desc_, detail: detail_});
  405. } else {
  406. de.reject({uri: uri_, desc: desc_});
  407. }
  408. }
  409. delete self._calls[o[1]];
  410. }
  411. else if (o[0] === ab._MESSAGE_TYPEID_EVENT)
  412. {
  413. var subid = self._prefixes.resolve(o[1], true);
  414. if (subid in self._subscriptions) {
  415. var uri2 = o[1];
  416. var val = o[2];
  417. if (ab._debugpubsub) {
  418. console.group("WAMP Event");
  419. console.info(self._wsuri + " [" + self._session_id + "]");
  420. console.log(uri2);
  421. console.log(val);
  422. console.groupEnd();
  423. }
  424. self._subscriptions[subid].forEach(function (callback) {
  425. callback(uri2, val);
  426. });
  427. }
  428. else {
  429. // ignore unsolicited event!
  430. }
  431. }
  432. else if (o[0] === ab._MESSAGE_TYPEID_WELCOME)
  433. {
  434. if (self._session_id === null) {
  435. self._session_id = o[1];
  436. self._wamp_version = o[2];
  437. self._server = o[3];
  438. if (ab._debugrpc || ab._debugpubsub) {
  439. console.group("WAMP Welcome");
  440. console.info(self._wsuri + " [" + self._session_id + "]");
  441. console.log(self._wamp_version);
  442. console.log(self._server);
  443. console.groupEnd();
  444. }
  445. // only now that we have received the initial server-to-client
  446. // welcome message, fire application onopen() hook
  447. if (self._websocket_onopen !== null) {
  448. self._websocket_onopen();
  449. }
  450. } else {
  451. throw "protocol error (welcome message received more than once)";
  452. }
  453. }
  454. };
  455. self._websocket.onopen = function (e)
  456. {
  457. // check if we can speak WAMP!
  458. if (self._websocket.protocol !== ab._subprotocol) {
  459. if (typeof self._websocket.protocol === 'undefined') {
  460. // i.e. Safari does subprotocol negotiation (broken), but then
  461. // does NOT set the protocol attribute of the websocket object (broken)
  462. //
  463. if (ab._debugws) {
  464. console.group("WS Warning");
  465. console.info(self._wsuri);
  466. console.log("WebSocket object has no protocol attribute: WAMP subprotocol check skipped!");
  467. console.groupEnd();
  468. }
  469. }
  470. else if (self._options && self._options.skipSubprotocolCheck) {
  471. // WAMP subprotocol check disabled by session option
  472. //
  473. if (ab._debugws) {
  474. console.group("WS Warning");
  475. console.info(self._wsuri);
  476. console.log("Server does not speak WAMP, but subprotocol check disabled by option!");
  477. console.log(self._websocket.protocol);
  478. console.groupEnd();
  479. }
  480. } else {
  481. // we only speak WAMP .. if the server denied us this, we bail out.
  482. //
  483. self._websocket.close(1000, "server does not speak WAMP");
  484. throw "server does not speak WAMP (but '" + self._websocket.protocol + "' !)";
  485. }
  486. }
  487. if (ab._debugws) {
  488. console.group("WAMP Connect");
  489. console.info(self._wsuri);
  490. console.log(self._websocket.protocol);
  491. console.groupEnd();
  492. }
  493. self._websocket_connected = true;
  494. };
  495. self._websocket.onerror = function (e)
  496. {
  497. // FF fires this upon unclean closes
  498. // Chrome does not fire this
  499. };
  500. self._websocket.onclose = function (e)
  501. {
  502. if (ab._debugws) {
  503. if (self._websocket_connected) {
  504. console.log("Autobahn connection to " + self._wsuri + " lost (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ").");
  505. } else {
  506. console.log("Autobahn could not connect to " + self._wsuri + " (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ").");
  507. }
  508. }
  509. // fire app callback
  510. if (self._websocket_onclose !== undefined) {
  511. if (self._websocket_connected) {
  512. if (e.wasClean) {
  513. // connection was closed cleanly (closing HS was performed)
  514. self._websocket_onclose(ab.CONNECTION_CLOSED, "WS-" + e.code + ": " + e.reason);
  515. } else {
  516. // connection was closed uncleanly (lost without closing HS)
  517. self._websocket_onclose(ab.CONNECTION_LOST);
  518. }
  519. } else {
  520. // connection could not be established in the first place
  521. self._websocket_onclose(ab.CONNECTION_UNREACHABLE);
  522. }
  523. }
  524. // cleanup - reconnect requires a new session object!
  525. self._websocket_connected = false;
  526. self._wsuri = null;
  527. self._websocket_onopen = null;
  528. self._websocket_onclose = null;
  529. self._websocket = null;
  530. };
  531. self.log = function () {
  532. if (self._options && 'sessionIdent' in self._options) {
  533. console.group("WAMP Session '" + self._options.sessionIdent + "' [" + self._session_id + "]");
  534. } else {
  535. console.group("WAMP Session " + "[" + self._session_id + "]");
  536. }
  537. for (var i = 0; i < arguments.length; ++i) {
  538. console.log(arguments[i]);
  539. }
  540. console.groupEnd();
  541. };
  542. };
  543. ab.Session.prototype._send = function (msg) {
  544. var self = this;
  545. if (!self._websocket_connected) {
  546. throw "Autobahn not connected";
  547. }
  548. var rmsg;
  549. switch(true)
  550. {
  551. // In the event that prototype library is in existance run the toJSON method prototype provides
  552. // else run the standard JSON.stringify
  553. // this is a very clever problem that causes json to be double-quote-encoded.
  554. case window.Prototype && typeof top.window.__prototype_deleted === 'undefined':
  555. case typeof msg.toJSON === 'function':
  556. rmsg = msg.toJSON();
  557. break;
  558. // we could do instead
  559. // msg.toJSON = function(){return msg};
  560. // rmsg = JSON.stringify(msg);
  561. default:
  562. rmsg = JSON.stringify(msg);
  563. }
  564. self._websocket.send(rmsg);
  565. self._txcnt += 1;
  566. if (ab._debugws) {
  567. console.group("WS Send");
  568. console.info(self._wsuri + " [" + self._session_id + "]");
  569. console.log(self._txcnt);
  570. console.log(rmsg);
  571. console.groupEnd();
  572. }
  573. };
  574. ab.Session.prototype.close = function () {
  575. var self = this;
  576. if (self._websocket_connected) {
  577. self._websocket.close();
  578. } else {
  579. //throw "Autobahn not connected";
  580. }
  581. };
  582. ab.Session.prototype.sessionid = function () {
  583. var self = this;
  584. return self._session_id;
  585. };
  586. ab.Session.prototype.wsuri = function () {
  587. var self = this;
  588. return self._wsuri;
  589. };
  590. ab.Session.prototype.shrink = function (uri, pass) {
  591. var self = this;
  592. if (pass === undefined) pass = true;
  593. return self._prefixes.shrink(uri, pass);
  594. };
  595. ab.Session.prototype.resolve = function (curie, pass) {
  596. var self = this;
  597. if (pass === undefined) pass = true;
  598. return self._prefixes.resolve(curie, pass);
  599. };
  600. ab.Session.prototype.prefix = function (prefix, uri) {
  601. var self = this;
  602. /*
  603. if (self._prefixes.get(prefix) !== undefined) {
  604. throw "prefix '" + prefix + "' already defined";
  605. }
  606. */
  607. self._prefixes.set(prefix, uri);
  608. if (ab._debugrpc || ab._debugpubsub) {
  609. console.group("WAMP Prefix");
  610. console.info(self._wsuri + " [" + self._session_id + "]");
  611. console.log(prefix);
  612. console.log(uri);
  613. console.groupEnd();
  614. }
  615. var msg = [ab._MESSAGE_TYPEID_PREFIX, prefix, uri];
  616. self._send(msg);
  617. };
  618. ab.Session.prototype.call = function () {
  619. var self = this;
  620. var d = new ab.Deferred();
  621. var callid;
  622. while (true) {
  623. callid = ab._newidFast();
  624. if (!(callid in self._calls)) {
  625. break;
  626. }
  627. }
  628. self._calls[callid] = d;
  629. var procuri = self._prefixes.shrink(arguments[0], true);
  630. var obj = [ab._MESSAGE_TYPEID_CALL, callid, procuri];
  631. for (var i = 1; i < arguments.length; i += 1) {
  632. obj.push(arguments[i]);
  633. }
  634. self._send(obj);
  635. if (ab._debugrpc) {
  636. d._ab_callobj = obj;
  637. d._ab_tid = self._wsuri + " [" + self._session_id + "][" + callid + "]";
  638. console.time(d._ab_tid);
  639. console.info();
  640. }
  641. if (d.promise.then) {
  642. // whenjs has the actual user promise in an attribute
  643. return d.promise;
  644. } else {
  645. return d;
  646. }
  647. };
  648. ab.Session.prototype.subscribe = function (topicuri, callback) {
  649. var self = this;
  650. // subscribe by sending WAMP message when topic not already subscribed
  651. //
  652. var rtopicuri = self._prefixes.resolve(topicuri, true);
  653. if (!(rtopicuri in self._subscriptions)) {
  654. if (ab._debugpubsub) {
  655. console.group("WAMP Subscribe");
  656. console.info(self._wsuri + " [" + self._session_id + "]");
  657. console.log(topicuri);
  658. console.log(callback);
  659. console.groupEnd();
  660. }
  661. var msg = [ab._MESSAGE_TYPEID_SUBSCRIBE, topicuri];
  662. self._send(msg);
  663. self._subscriptions[rtopicuri] = [];
  664. }
  665. // add callback to event listeners list if not already in list
  666. //
  667. var i = self._subscriptions[rtopicuri].indexOf(callback);
  668. if (i === -1) {
  669. self._subscriptions[rtopicuri].push(callback);
  670. }
  671. else {
  672. throw "callback " + callback + " already subscribed for topic " + rtopicuri;
  673. }
  674. };
  675. ab.Session.prototype.unsubscribe = function (topicuri, callback) {
  676. var self = this;
  677. var rtopicuri = self._prefixes.resolve(topicuri, true);
  678. if (!(rtopicuri in self._subscriptions)) {
  679. throw "not subscribed to topic " + rtopicuri;
  680. }
  681. else {
  682. var removed;
  683. if (callback !== undefined) {
  684. var idx = self._subscriptions[rtopicuri].indexOf(callback);
  685. if (idx !== -1) {
  686. removed = callback;
  687. self._subscriptions[rtopicuri].splice(idx, 1);
  688. }
  689. else {
  690. throw "no callback " + callback + " subscribed on topic " + rtopicuri;
  691. }
  692. }
  693. else {
  694. removed = self._subscriptions[rtopicuri].slice();
  695. self._subscriptions[rtopicuri] = [];
  696. }
  697. if (self._subscriptions[rtopicuri].length === 0) {
  698. delete self._subscriptions[rtopicuri];
  699. if (ab._debugpubsub) {
  700. console.group("WAMP Unsubscribe");
  701. console.info(self._wsuri + " [" + self._session_id + "]");
  702. console.log(topicuri);
  703. console.log(removed);
  704. console.groupEnd();
  705. }
  706. var msg = [ab._MESSAGE_TYPEID_UNSUBSCRIBE, topicuri];
  707. self._send(msg);
  708. }
  709. }
  710. };
  711. ab.Session.prototype.publish = function () {
  712. var self = this;
  713. var topicuri = arguments[0];
  714. var event = arguments[1];
  715. var excludeMe = null;
  716. var exclude = null;
  717. var eligible = null;
  718. var msg = null;
  719. if (arguments.length > 3) {
  720. if (!(arguments[2] instanceof Array)) {
  721. throw "invalid argument type(s)";
  722. }
  723. if (!(arguments[3] instanceof Array)) {
  724. throw "invalid argument type(s)";
  725. }
  726. exclude = arguments[2];
  727. eligible = arguments[3];
  728. msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude, eligible];
  729. } else if (arguments.length > 2) {
  730. if (typeof(arguments[2]) === 'boolean') {
  731. excludeMe = arguments[2];
  732. msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, excludeMe];
  733. } else if (arguments[2] instanceof Array) {
  734. exclude = arguments[2];
  735. msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude];
  736. } else {
  737. throw "invalid argument type(s)";
  738. }
  739. } else {
  740. msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event];
  741. }
  742. if (ab._debugpubsub) {
  743. console.group("WAMP Publish");
  744. console.info(self._wsuri + " [" + self._session_id + "]");
  745. console.log(topicuri);
  746. console.log(event);
  747. if (excludeMe !== null) {
  748. console.log(excludeMe);
  749. } else {
  750. if (exclude !== null) {
  751. console.log(exclude);
  752. if (eligible !== null) {
  753. console.log(eligible);
  754. }
  755. }
  756. }
  757. console.groupEnd();
  758. }
  759. self._send(msg);
  760. };
  761. // allow both 2-party and 3-party authentication/authorization
  762. // for 3-party: let C sign, but let both the B and C party authorize
  763. ab.Session.prototype.authreq = function (appkey, extra) {
  764. return this.call("http://api.wamp.ws/procedure#authreq", appkey, extra);
  765. };
  766. ab.Session.prototype.authsign = function (challenge, secret) {
  767. if (!secret) {
  768. secret = "";
  769. }
  770. return CryptoJS.HmacSHA256(challenge, secret).toString(CryptoJS.enc.Base64);
  771. };
  772. ab.Session.prototype.auth = function (signature) {
  773. return this.call("http://api.wamp.ws/procedure#auth", signature);
  774. };
  775. ab._connect = function (peer) {
  776. // establish session to WAMP server
  777. var sess = new ab.Session(peer.wsuri,
  778. // fired when session has been opened
  779. function() {
  780. peer.connects += 1;
  781. peer.retryCount = 0;
  782. // we are connected .. do awesome stuff!
  783. peer.onConnect(sess);
  784. },
  785. // fired when session has been closed
  786. function(code, reason) {
  787. var stop = null;
  788. switch (code) {
  789. case ab.CONNECTION_CLOSED:
  790. // the session was closed by the app
  791. peer.onHangup(code, "Connection was closed properly [" + reason + "]");
  792. break;
  793. case ab.CONNECTION_UNSUPPORTED:
  794. // fatal: we miss our WebSocket object!
  795. peer.onHangup(code, "Browser does not support WebSocket.");
  796. break;
  797. case ab.CONNECTION_UNREACHABLE:
  798. peer.retryCount += 1;
  799. if (peer.connects === 0) {
  800. // the connection could not be established in the first place
  801. // which likely means invalid server WS URI or such things
  802. peer.onHangup(code, "Connection could not be established.");
  803. } else {
  804. // the connection was established at least once successfully,
  805. // but now lost .. sane thing is to try automatic reconnects
  806. if (peer.retryCount <= peer.options.maxRetries) {
  807. // notify the app of scheduled reconnect
  808. stop = peer.onHangup(ab.CONNECTION_UNREACHABLE_SCHEDULED_RECONNECT,
  809. "Connection unreachable - scheduled reconnect to occur in " + (peer.options.retryDelay / 1000) + " second(s) - attempt " + peer.retryCount + " of " + peer.options.maxRetries + ".",
  810. {delay: peer.options.retryDelay,
  811. retries: peer.retryCount,
  812. maxretries: peer.options.maxRetries});
  813. if (!stop) {
  814. if (ab._debugconnect) {
  815. console.log("Connection unreachable - retrying (" + peer.retryCount + ") ..");
  816. }
  817. window.setTimeout(ab._connect, peer.options.retryDelay, peer);
  818. } else {
  819. if (ab._debugconnect) {
  820. console.log("Connection unreachable - retrying stopped by app");
  821. }
  822. peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Number of connection retries exceeded.");
  823. }
  824. } else {
  825. peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Number of connection retries exceeded.");
  826. }
  827. }
  828. break;
  829. case ab.CONNECTION_LOST:
  830. peer.retryCount += 1;
  831. if (peer.retryCount <= peer.options.maxRetries) {
  832. // notify the app of scheduled reconnect
  833. stop = peer.onHangup(ab.CONNECTION_LOST_SCHEDULED_RECONNECT,
  834. "Connection lost - scheduled " + peer.retryCount + "th reconnect to occur in " + (peer.options.retryDelay / 1000) + " second(s).",
  835. {delay: peer.options.retryDelay,
  836. retries: peer.retryCount,
  837. maxretries: peer.options.maxRetries});
  838. if (!stop) {
  839. if (ab._debugconnect) {
  840. console.log("Connection lost - retrying (" + peer.retryCount + ") ..");
  841. }
  842. window.setTimeout(ab._connect, peer.options.retryDelay, peer);
  843. } else {
  844. if (ab._debugconnect) {
  845. console.log("Connection lost - retrying stopped by app");
  846. }
  847. peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Connection lost.");
  848. }
  849. } else {
  850. peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Connection lost.");
  851. }
  852. break;
  853. default:
  854. throw "unhandled close code in ab._connect";
  855. }
  856. },
  857. peer.options // forward options to session class for specific WS/WAMP options
  858. );
  859. };
  860. ab.connect = function (wsuri, onconnect, onhangup, options) {
  861. var peer = {};
  862. peer.wsuri = wsuri;
  863. if (!options) {
  864. peer.options = {};
  865. } else {
  866. peer.options = options;
  867. }
  868. if (peer.options.retryDelay === undefined) {
  869. peer.options.retryDelay = 5000;
  870. }
  871. if (peer.options.maxRetries === undefined) {
  872. peer.options.maxRetries = 10;
  873. }
  874. if (peer.options.skipSubprotocolCheck === undefined) {
  875. peer.options.skipSubprotocolCheck = false;
  876. }
  877. if (peer.options.skipSubprotocolAnnounce === undefined) {
  878. peer.options.skipSubprotocolAnnounce = false;
  879. }
  880. if (!onconnect) {
  881. throw "onConnect handler required!";
  882. } else {
  883. peer.onConnect = onconnect;
  884. }
  885. if (!onhangup) {
  886. peer.onHangup = function (code, reason, detail) {
  887. if (ab._debugconnect) {
  888. console.log(code, reason, detail);
  889. }
  890. };
  891. } else {
  892. peer.onHangup = onhangup;
  893. }
  894. peer.connects = 0; // total number of successful connects
  895. peer.retryCount = 0; // number of retries since last successful connect
  896. ab._connect(peer);
  897. };
  898. ab.launch = function (appConfig, onOpen, onClose) {
  899. function Rpc(session, uri) {
  900. return function() {
  901. var args = [uri];
  902. for (var j = 0; j < arguments.length; ++j) {
  903. args.push(arguments[j]);
  904. }
  905. //arguments.unshift(uri);
  906. return ab.Session.prototype.call.apply(session, args);
  907. };
  908. }
  909. function createApi(session, perms) {
  910. session.api = {};
  911. for (var i = 0; i < perms.rpc.length; ++i) {
  912. var uri = perms.rpc[i].uri;
  913. var _method = uri.split("#")[1];
  914. var _class = uri.split("#")[0].split("/");
  915. _class = _class[_class.length - 1];
  916. if (!(_class in session.api)) {
  917. session.api[_class] = {};
  918. }
  919. session.api[_class][_method] = new Rpc(session, uri);
  920. }
  921. }
  922. ab.connect(appConfig.wsuri,
  923. // connection established handler
  924. function (session) {
  925. if (!appConfig.appkey || appConfig.appkey === "") {
  926. // Authenticate as anonymous ..
  927. session.authreq().then(function () {
  928. session.auth().then(function (permissions) {
  929. //createApi(session, permissions);
  930. if (onOpen) {
  931. onOpen(session);
  932. } else if (ab._debugconnect) {
  933. session.log('Session opened.');
  934. }
  935. }, session.log);
  936. }, session.log);
  937. } else {
  938. // Authenticate as appkey ..
  939. session.authreq(appConfig.appkey, appConfig.appextra).then(function (challenge) {
  940. var signature = null;
  941. if (typeof(appConfig.appsecret) === 'function') {
  942. signature = appConfig.appsecret(challenge);
  943. } else {
  944. // derive secret if salted WAMP-CRA
  945. var secret = ab.deriveKey(appConfig.appsecret, JSON.parse(challenge).authextra);
  946. // direct sign
  947. signature = session.authsign(challenge, secret);
  948. }
  949. session.auth(signature).then(function (permissions) {
  950. //createApi(session, permissions);
  951. if (onOpen) {
  952. onOpen(session);
  953. } else if (ab._debugconnect) {
  954. session.log('Session opened.');
  955. }
  956. }, session.log);
  957. }, session.log);
  958. }
  959. },
  960. // connection lost handler
  961. function (code, reason, detail) {
  962. if (onClose) {
  963. onClose(code, reason, detail);
  964. } else if (ab._debugconnect) {
  965. ab.log('Session closed.', code, reason, detail);
  966. }
  967. },
  968. // WAMP session config
  969. appConfig.sessionConfig
  970. );
  971. };