Created
March 31, 2017 14:37
-
-
Save cboden/fcae978cfc016d506639c5241f94e772 to your computer and use it in GitHub Desktop.
Autobahn v1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @license MIT License (c) 2011,2012 Copyright Tavendo GmbH. */ | |
/** | |
* AutobahnJS - http://autobahn.ws | |
* | |
* A lightweight implementation of | |
* | |
* WAMP (The WebSocket Application Messaging Protocol) - http://wamp.ws | |
* | |
* Provides asynchronous RPC/PubSub over WebSocket. | |
* | |
* Copyright 2011, 2012 Tavendo GmbH. Licensed under the MIT License. | |
* See license text at http://www.opensource.org/licenses/mit-license.php | |
*/ | |
"use strict"; | |
/** @define {string} */ | |
var AUTOBAHNJS_VERSION = '?.?.?'; | |
/** @define {boolean} */ | |
var AUTOBAHNJS_DEBUG = true; | |
var ab = window.ab = {}; | |
ab._version = AUTOBAHNJS_VERSION; | |
/** | |
* Fallbacks for browsers lacking | |
* | |
* Array.prototype.indexOf | |
* Array.prototype.forEach | |
* | |
* most notably MSIE8. | |
* | |
* Source: | |
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf | |
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach | |
*/ | |
(function () { | |
if (!Array.prototype.indexOf) { | |
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { | |
"use strict"; | |
if (this === null) { | |
throw new TypeError(); | |
} | |
var t = new Object(this); | |
var len = t.length >>> 0; | |
if (len === 0) { | |
return -1; | |
} | |
var n = 0; | |
if (arguments.length > 0) { | |
n = Number(arguments[1]); | |
if (n !== n) { // shortcut for verifying if it's NaN | |
n = 0; | |
} else if (n !== 0 && n !== Infinity && n !== -Infinity) { | |
n = (n > 0 || -1) * Math.floor(Math.abs(n)); | |
} | |
} | |
if (n >= len) { | |
return -1; | |
} | |
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); | |
for (; k < len; k++) { | |
if (k in t && t[k] === searchElement) { | |
return k; | |
} | |
} | |
return -1; | |
}; | |
} | |
if (!Array.prototype.forEach) { | |
Array.prototype.forEach = function (callback, thisArg) { | |
var T, k; | |
if (this === null) { | |
throw new TypeError(" this is null or not defined"); | |
} | |
// 1. Let O be the result of calling ToObject passing the |this| value as the argument. | |
var O = new Object(this); | |
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". | |
// 3. Let len be ToUint32(lenValue). | |
var len = O.length >>> 0; // Hack to convert O.length to a UInt32 | |
// 4. If IsCallable(callback) is false, throw a TypeError exception. | |
// See: http://es5.github.com/#x9.11 | |
if ({}.toString.call(callback) !== "[object Function]") { | |
throw new TypeError(callback + " is not a function"); | |
} | |
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined. | |
if (thisArg) { | |
T = thisArg; | |
} | |
// 6. Let k be 0 | |
k = 0; | |
// 7. Repeat, while k < len | |
while (k < len) { | |
var kValue; | |
// a. Let Pk be ToString(k). | |
// This is implicit for LHS operands of the in operator | |
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. | |
// This step can be combined with c | |
// c. If kPresent is true, then | |
if (k in O) { | |
// i. Let kValue be the result of calling the Get internal method of O with argument Pk. | |
kValue = O[k]; | |
// ii. Call the Call internal method of callback with T as the this value and | |
// argument list containing kValue, k, and O. | |
callback.call(T, kValue, k, O); | |
} | |
// d. Increase k by 1. | |
k++; | |
} | |
// 8. return undefined | |
}; | |
} | |
})(); | |
// Helper to slice out browser / version from userAgent | |
ab._sliceUserAgent = function (str, delim, delim2) { | |
var ver = []; | |
var ua = navigator.userAgent; | |
var i = ua.indexOf(str); | |
var j = ua.indexOf(delim, i); | |
if (j < 0) { | |
j = ua.length; | |
} | |
var agent = ua.slice(i, j).split(delim2); | |
var v = agent[1].split('.'); | |
for (var k = 0; k < v.length; ++k) { | |
ver.push(parseInt(v[k], 10)); | |
} | |
return {name: agent[0], version: ver}; | |
}; | |
/** | |
* Detect browser and browser version. | |
*/ | |
ab.getBrowser = function () { | |
var ua = navigator.userAgent; | |
if (ua.indexOf("Chrome") > -1) { | |
return ab._sliceUserAgent("Chrome", " ", "/"); | |
} else if (ua.indexOf("Safari") > -1) { | |
return ab._sliceUserAgent("Safari", " ", "/"); | |
} else if (ua.indexOf("Firefox") > -1) { | |
return ab._sliceUserAgent("Firefox", " ", "/"); | |
} else if (ua.indexOf("MSIE") > -1) { | |
return ab._sliceUserAgent("MSIE", ";", " "); | |
} else { | |
return null; | |
} | |
}; | |
// Logging message for unsupported browser. | |
ab.browserNotSupportedMessage = "Browser does not support WebSockets (RFC6455)"; | |
ab._idchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |
ab._idlen = 16; | |
ab._subprotocol = "wamp"; | |
ab._newid = function () { | |
var id = ""; | |
for (var i = 0; i < ab._idlen; i += 1) { | |
id += ab._idchars.charAt(Math.floor(Math.random() * ab._idchars.length)); | |
} | |
return id; | |
}; | |
ab.log = function (o) { | |
if (window.console && console.log) { | |
//console.log.apply(console, !!arguments.length ? arguments : [this]); | |
if (arguments.length > 1) { | |
console.group("Log Item"); | |
for (var i = 0; i < arguments.length; i += 1) { | |
console.log(arguments[i]); | |
} | |
console.groupEnd(); | |
} else { | |
console.log(arguments[0]); | |
} | |
} | |
}; | |
ab._debugrpc = false; | |
ab._debugpubsub = false; | |
ab._debugws = false; | |
ab.debug = function (debugWamp, debugWs) { | |
if ("console" in window) { | |
ab._debugrpc = debugWamp; | |
ab._debugpubsub = debugWamp; | |
ab._debugws = debugWs; | |
} else { | |
throw "browser does not support console object"; | |
} | |
}; | |
ab.version = function () { | |
return ab._version; | |
}; | |
ab.PrefixMap = function () { | |
var self = this; | |
self._index = {}; | |
self._rindex = {}; | |
}; | |
ab.PrefixMap.prototype.get = function (prefix) { | |
var self = this; | |
return self._index[prefix]; | |
}; | |
ab.PrefixMap.prototype.set = function (prefix, uri) { | |
var self = this; | |
self._index[prefix] = uri; | |
self._rindex[uri] = prefix; | |
}; | |
ab.PrefixMap.prototype.setDefault = function (uri) { | |
var self = this; | |
self._index[""] = uri; | |
self._rindex[uri] = ""; | |
}; | |
ab.PrefixMap.prototype.remove = function (prefix) { | |
var self = this; | |
var uri = self._index[prefix]; | |
if (uri) { | |
delete self._index[prefix]; | |
delete self._rindex[uri]; | |
} | |
}; | |
ab.PrefixMap.prototype.resolve = function (curie, pass) { | |
var self = this; | |
// skip if not a CURIE | |
var i = curie.indexOf(":"); | |
if (i >= 0) { | |
var prefix = curie.substring(0, i); | |
if (self._index[prefix]) { | |
return self._index[prefix] + curie.substring(i + 1); | |
} | |
} | |
// either pass-through or null | |
if (pass == true) { | |
return curie; | |
} else { | |
return null; | |
} | |
}; | |
ab.PrefixMap.prototype.shrink = function (uri, pass) { | |
var self = this; | |
// skip if already a CURIE | |
var i = uri.indexOf(":"); | |
if (i == -1) { | |
for (var i = uri.length; i > 0; i -= 1) { | |
var u = uri.substring(0, i); | |
var p = self._rindex[u]; | |
if (p) { | |
return p + ":" + uri.substring(i); | |
} | |
} | |
} | |
// either pass-through or null | |
if (pass == true) { | |
return uri; | |
} else { | |
return null; | |
} | |
}; | |
ab._MESSAGE_TYPEID_WELCOME = 0; | |
ab._MESSAGE_TYPEID_PREFIX = 1; | |
ab._MESSAGE_TYPEID_CALL = 2; | |
ab._MESSAGE_TYPEID_CALL_RESULT = 3; | |
ab._MESSAGE_TYPEID_CALL_ERROR = 4; | |
ab._MESSAGE_TYPEID_SUBSCRIBE = 5; | |
ab._MESSAGE_TYPEID_UNSUBSCRIBE = 6; | |
ab._MESSAGE_TYPEID_PUBLISH = 7; | |
ab._MESSAGE_TYPEID_EVENT = 8; | |
ab.CONNECTION_CLOSED = 0; | |
ab.CONNECTION_LOST = 1; | |
ab.CONNECTION_UNREACHABLE = 2; | |
ab.CONNECTION_UNSUPPORTED = 3; | |
ab.Session = function (wsuri, onopen, onclose, options) { | |
var self = this; | |
self._wsuri = wsuri; | |
self._options = options; | |
self._websocket_onopen = onopen; | |
self._websocket_onclose = onclose; | |
self._websocket = null; | |
self._websocket_connected = false; | |
self._session_id = null; | |
self._calls = {}; | |
self._subscriptions = {}; | |
self._prefixes = new ab.PrefixMap(); | |
self._txcnt = 0; | |
self._rxcnt = 0; | |
if ("WebSocket" in window) { | |
// Chrome, MSIE, newer Firefox | |
self._websocket = new WebSocket(self._wsuri, [ab._subprotocol]); | |
} else if ("MozWebSocket" in window) { | |
// older versions of Firefox prefix the WebSocket object | |
self._websocket = new MozWebSocket(self._wsuri, [ab._subprotocol]); | |
} else { | |
if (onclose !== undefined) { | |
onclose(ab.CONNECTION_UNSUPPORTED); | |
return; | |
} else { | |
throw ab.browserNotSupportedMessage; | |
} | |
} | |
self._websocket.onmessage = function (e) | |
{ | |
if (ab._debugws) { | |
self._rxcnt += 1; | |
console.group("WS Receive"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(self._rxcnt); | |
console.log(e.data); | |
console.groupEnd(); | |
} | |
var o = JSON.parse(e.data); | |
if (o[1] in self._calls) | |
{ | |
if (o[0] === ab._MESSAGE_TYPEID_CALL_RESULT) { | |
var dr = self._calls[o[1]]; | |
var r = o[2]; | |
if (ab._debugrpc && dr._ab_callobj !== undefined) { | |
console.group("WAMP Call", dr._ab_callobj[2]); | |
console.timeEnd(dr._ab_tid); | |
console.group("Arguments"); | |
for (var i = 3; i < dr._ab_callobj.length; i += 1) { | |
var arg = dr._ab_callobj[i]; | |
if (arg !== undefined) { | |
console.log(arg); | |
} else { | |
break; | |
} | |
} | |
console.groupEnd(); | |
console.group("Result"); | |
console.log(r); | |
console.groupEnd(); | |
console.groupEnd(); | |
} | |
dr.resolve(r); | |
} | |
else if (o[0] === ab._MESSAGE_TYPEID_CALL_ERROR) { | |
var de = self._calls[o[1]]; | |
var uri = o[2]; | |
var desc = o[3]; | |
var detail = o[4]; | |
if (ab._debugrpc && de._ab_callobj !== undefined) { | |
console.group("WAMP Call", de._ab_callobj[2]); | |
console.timeEnd(de._ab_tid); | |
console.group("Arguments"); | |
for (var j = 3; j < de._ab_callobj.length; j += 1) { | |
var arg2 = de._ab_callobj[j]; | |
if (arg2 !== undefined) { | |
console.log(arg2); | |
} else { | |
break; | |
} | |
} | |
console.groupEnd(); | |
console.group("Error"); | |
console.log(uri); | |
console.log(desc); | |
if (detail !== undefined) { | |
console.log(detail); | |
} | |
console.groupEnd(); | |
console.groupEnd(); | |
} | |
if (detail !== undefined) { | |
de.reject(uri, desc, detail); | |
} else { | |
de.reject(uri, desc); | |
} | |
} | |
delete self._calls[o[1]]; | |
} | |
else if (o[0] === ab._MESSAGE_TYPEID_EVENT) | |
{ | |
var subid = self._prefixes.resolve(o[1], true); | |
if (subid in self._subscriptions) { | |
var uri2 = o[1]; | |
var val = o[2]; | |
if (ab._debugpubsub) { | |
console.group("WAMP Event"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(uri2); | |
console.log(val); | |
console.groupEnd(); | |
} | |
self._subscriptions[subid].forEach(function (callback) { | |
callback(uri2, val); | |
}); | |
} | |
else { | |
// ignore unsolicited event! | |
} | |
} | |
else if (o[0] === ab._MESSAGE_TYPEID_WELCOME) | |
{ | |
if (self._session_id === null) { | |
self._session_id = o[1]; | |
self._wamp_version = o[2]; | |
self._server = o[3]; | |
if (ab._debugrpc || ab._debugpubsub) { | |
console.group("WAMP Welcome"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(self._wamp_version); | |
console.log(self._server); | |
console.groupEnd(); | |
} | |
// only now that we have received the initial server-to-client | |
// welcome message, fire application onopen() hook | |
if (self._websocket_onopen !== null) { | |
self._websocket_onopen(self._session_id, self._wamp_version, self._server); | |
} | |
} else { | |
throw "protocol error (welcome message received more than once)"; | |
} | |
} | |
}; | |
self._websocket.onopen = function (e) | |
{ | |
// check if we can speak WAMP! | |
if (self._websocket.protocol !== ab._subprotocol) { | |
if (typeof self._websocket.protocol === 'undefined') { | |
// i.e. Safari does subprotocol negotiation (broken), but then | |
// does NOT set the protocol attribute of the websocket object (broken) | |
// | |
if (ab._debugws) { | |
console.group("WS Warning"); | |
console.info(self._wsuri); | |
console.log("WebSocket object has no protocol attribute: WAMP subprotocol check skipped!"); | |
console.groupEnd(); | |
} | |
} | |
else if (self._options && self._options.skipSubprotocolCheck) { | |
// WAMP subprotocol check disabled by session option | |
// | |
if (ab._debugws) { | |
console.group("WS Warning"); | |
console.info(self._wsuri); | |
console.log("Server does not speak WAMP, but subprotocol check disabled by option!"); | |
console.log(self._websocket.protocol); | |
console.groupEnd(); | |
} | |
} else { | |
// we only speak WAMP .. if the server denied us this, we bail out. | |
// | |
self._websocket.close(1000, "server does not speak WAMP"); | |
throw "server does not speak WAMP (but '" + self._websocket.protocol + "' !)"; | |
} | |
} | |
if (ab._debugws) { | |
console.group("WAMP Connect"); | |
console.info(self._wsuri); | |
console.log(self._websocket.protocol); | |
console.groupEnd(); | |
} | |
self._websocket_connected = true; | |
}; | |
self._websocket.onerror = function (e) | |
{ | |
// FF fires this upon unclean closes | |
// Chrome does not fire this | |
}; | |
self._websocket.onclose = function (e) | |
{ | |
if (ab._debugws) { | |
if (self._websocket_connected) { | |
console.log("Autobahn connection to " + self._wsuri + " lost (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ")."); | |
} else { | |
console.log("Autobahn could not connect to " + self._wsuri + " (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ")."); | |
} | |
} | |
// fire app callback | |
if (self._websocket_onclose !== undefined) { | |
if (self._websocket_connected) { | |
if (e.wasClean) { | |
// connection was closed cleanly (closing HS was performed) | |
self._websocket_onclose(ab.CONNECTION_CLOSED); | |
} else { | |
// connection was closed uncleanly (lost without closing HS) | |
self._websocket_onclose(ab.CONNECTION_LOST); | |
} | |
} else { | |
// connection could not be established in the first place | |
self._websocket_onclose(ab.CONNECTION_UNREACHABLE); | |
} | |
} | |
// cleanup - reconnect requires a new session object! | |
self._websocket_connected = false; | |
self._wsuri = null; | |
self._websocket_onopen = null; | |
self._websocket_onclose = null; | |
self._websocket = null; | |
}; | |
}; | |
ab.Session.prototype._send = function (msg) { | |
var self = this; | |
if (!self._websocket_connected) { | |
throw "Autobahn not connected"; | |
} | |
var rmsg = JSON.stringify(msg); | |
self._websocket.send(rmsg); | |
self._txcnt += 1; | |
if (ab._debugws) { | |
console.group("WS Send"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(self._txcnt); | |
console.log(rmsg); | |
console.groupEnd(); | |
} | |
}; | |
ab.Session.prototype.close = function () { | |
var self = this; | |
if (!self._websocket_connected) { | |
throw "Autobahn not connected"; | |
} | |
self._websocket.close(); | |
}; | |
ab.Session.prototype.sessionid = function () { | |
var self = this; | |
return self._session_id; | |
}; | |
ab.Session.prototype.shrink = function (uri, pass) { | |
var self = this; | |
return self._prefixes.shrink(uri, pass); | |
}; | |
ab.Session.prototype.resolve = function (curie, pass) { | |
var self = this; | |
return self._prefixes.resolve(curie, pass); | |
}; | |
ab.Session.prototype.prefix = function (prefix, uri) { | |
var self = this; | |
if (self._prefixes.get(prefix) !== undefined) { | |
throw "prefix '" + prefix + "' already defined"; | |
} | |
self._prefixes.set(prefix, uri); | |
if (ab._debugrpc || ab._debugpubsub) { | |
console.group("WAMP Prefix"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(prefix); | |
console.log(uri); | |
console.groupEnd(); | |
} | |
var msg = [ab._MESSAGE_TYPEID_PREFIX, prefix, uri]; | |
self._send(msg); | |
}; | |
ab.Session.prototype.call = function () { | |
var self = this; | |
var d = new when.defer(); | |
var callid; | |
while (true) { | |
callid = ab._newid(); | |
if (!(callid in self._calls)) { | |
break; | |
} | |
} | |
self._calls[callid] = d; | |
var procuri = self._prefixes.shrink(arguments[0], true); | |
var obj = [ab._MESSAGE_TYPEID_CALL, callid, procuri]; | |
for (var i = 1; i < arguments.length; i += 1) { | |
obj.push(arguments[i]); | |
} | |
self._send(obj); | |
if (ab._debugrpc) { | |
d._ab_callobj = obj; | |
d._ab_tid = self._wsuri + " [" + self._session_id + "][" + callid + "]"; | |
console.time(d._ab_tid); | |
console.info(); | |
} | |
return d; | |
}; | |
ab.Session.prototype.subscribe = function (topicuri, callback) { | |
var self = this; | |
// subscribe by sending WAMP message when topic not already subscribed | |
// | |
var rtopicuri = self._prefixes.resolve(topicuri, true); | |
if (!(rtopicuri in self._subscriptions)) { | |
if (ab._debugpubsub) { | |
console.group("WAMP Subscribe"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(topicuri); | |
console.log(callback); | |
console.groupEnd(); | |
} | |
var msg = [ab._MESSAGE_TYPEID_SUBSCRIBE, topicuri]; | |
self._send(msg); | |
self._subscriptions[rtopicuri] = []; | |
} | |
// add callback to event listeners list if not already in list | |
// | |
var i = self._subscriptions[rtopicuri].indexOf(callback); | |
if (i === -1) { | |
self._subscriptions[rtopicuri].push(callback); | |
} | |
else { | |
throw "callback " + callback + " already subscribed for topic " + rtopicuri; | |
} | |
}; | |
ab.Session.prototype.unsubscribe = function (topicuri, callback) { | |
var self = this; | |
var rtopicuri = self._prefixes.resolve(topicuri, true); | |
if (!(rtopicuri in self._subscriptions)) { | |
throw "not subscribed to topic " + rtopicuri; | |
} | |
else { | |
var removed; | |
if (callback !== undefined) { | |
var idx = self._subscriptions[rtopicuri].indexOf(callback); | |
if (idx !== -1) { | |
removed = callback; | |
self._subscriptions[rtopicuri].splice(idx, 1); | |
} | |
else { | |
throw "no callback " + callback + " subscribed on topic " + rtopicuri; | |
} | |
} | |
else { | |
removed = self._subscriptions[rtopicuri].slice(); | |
self._subscriptions[rtopicuri] = []; | |
} | |
if (self._subscriptions[rtopicuri].length === 0) { | |
delete self._subscriptions[rtopicuri]; | |
if (ab._debugpubsub) { | |
console.group("WAMP Unsubscribe"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(topicuri); | |
console.log(removed); | |
console.groupEnd(); | |
} | |
var msg = [ab._MESSAGE_TYPEID_UNSUBSCRIBE, topicuri]; | |
self._send(msg); | |
} | |
} | |
}; | |
ab.Session.prototype.publish = function () { | |
var self = this; | |
var topicuri = arguments[0]; | |
var event = arguments[1]; | |
var excludeMe = null; | |
var exclude = null; | |
var eligible = null; | |
var msg = null; | |
if (arguments.length > 3) { | |
if (!(arguments[2] instanceof Array)) { | |
throw "invalid argument type(s)"; | |
} | |
if (!(arguments[3] instanceof Array)) { | |
throw "invalid argument type(s)"; | |
} | |
exclude = arguments[2]; | |
eligible = arguments[3]; | |
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude, eligible]; | |
} else if (arguments.length > 2) { | |
if (typeof(arguments[2]) === 'boolean') { | |
excludeMe = arguments[2]; | |
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, excludeMe]; | |
} else if (arguments[2] instanceof Array) { | |
exclude = arguments[2]; | |
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude]; | |
} else { | |
throw "invalid argument type(s)"; | |
} | |
} else { | |
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event]; | |
} | |
if (ab._debugpubsub) { | |
console.group("WAMP Publish"); | |
console.info(self._wsuri + " [" + self._session_id + "]"); | |
console.log(topicuri); | |
console.log(event); | |
if (excludeMe !== null) { | |
console.log(excludeMe); | |
} else { | |
if (exclude !== null) { | |
console.log(exclude); | |
if (eligible !== null) { | |
console.log(eligible); | |
} | |
} | |
} | |
console.groupEnd(); | |
} | |
self._send(msg); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
tried using this with call, and it seems when library is missing
Uncaught ReferenceError: when is not defined