UNPKG

strophejs-plugins

Version:
467 lines (412 loc) 13.2 kB
/** * This program is distributed under the terms of the MIT license. * * Copyright 2011 (c) Pierre Guilleminot <pierre.guilleminot@gmail.com> */ /** * Jabber-RPC plugin (XEP-0009) * Allow the management of RPC */ Strophe.addConnectionPlugin("rpc", { _connection : undefined, _whitelistEnabled : false, jidWhiteList : [], nodeWhiteList : [], domainWhiteList : [], /** * Plugin init * * @param {Strophe.Connection} connection Strophe connection */ init: function(connection) { this._connection = connection; this._whitelistEnabled = false; this.jidWhiteList = []; this.nodeWhiteList = []; this.domainWhiteList = []; Strophe.addNamespace("RPC", "jabber:iq:rpc"); if (!connection.hasOwnProperty("disco")) { Strophe.warn("You need the discovery plugin " + "to have Jabber-RPC fully implemented."); } else { this._connection.disco.addIdentity("automation", "rpc"); this._connection.disco.addFeature(Strophe.NS.RPC); } }, /** * Add a jid or an array of jids to * the whitelist. * It's possible to use wildecards for the domain * or the node. ie: * *@* * myname@* * *@jabber.org * * @param {String|Array} jid */ addJidToWhiteList: function(jids) { if (typeof(jids.sort) !== "function") { jids = [jids]; } for (var i = 0; i < jids.length; i++) { var jid = jids[i]; if (jid === "*@*") { this._whitelistEnabled = false; return; } var node = Strophe.getNodeFromJid(jid); var domain = Strophe.getDomainFromJid(jid); if (jid) { if (node === "*") { this.domainWhiteList.push(domain); } else if (domain === "*") { this.nodeWhiteList.push(node); } else { this.jidWhiteList.push(jid); } this._whitelistEnabled = true; } } }, /** * Helper to filter out Jid outside of the whitelists * * @param {String} jid Jid to test * @return {Boolean} */ _jidInWhitelist: function(jid) { if (!this._whitelistEnabled) return true; if (jid === this._connection.jid) return true; return ( this.domainWhiteList.indexOf(Strophe.getDomainFromJid(jid)) !== -1 || this.nodeWhiteList.indexOf(Strophe.getNodeFromJid(jid)) !== -1 || this.jidWhiteList.indexOf(jid) !== -1 ); }, /** * Send a request message * * @param {Object} id ID of the request * @param {String} to JID of the recipient * @param {String} method name of the method * @param {Array} params Array of parameters */ sendRequest: function(id, to, method, params) { params = (typeof(params.sort) === "function") ? params : [params]; var element = new Strophe.Builder("methodCall") .c("methodName").t(method) .up() .c("params"); for (var i = 0; i < params.length; i++) { element.c("param") .cnode(this._convertToXML(params[i])) .up().up(); } this.sendXMLElement(id, to, "set", element.tree()); }, /** * Send a response message * * @param {String} id ID of the request * @param {String} to JID of the recipient * @param {Object} result Result of the function call */ sendResponse: function(id, to, result) { var element = new Strophe.Builder("methodResponse") .c("params") .c("param") .cnode(this._convertToXML(result)); this.sendXMLElement(id, to, "result", element.tree()); }, /** * Send an error response (or fault) * * @param {String} id ID of the faulted request * @param {String} to JID of the recipient * @param {Number} code Code of the error * @param {String} message Error message */ sendError: function(id, to, code, message) { if (isNaN(code)) { Strophe.error(code + " is not a number"); return; } var element = new Strophe.Builder("methodResponse") .c("fault") .cnode(this._convertToXML({ faultCode: code, faultString: String(message) })); this.sendXMLElement(id, to, "result", element.tree()); }, /** * Send a XMLElement as the request * Does not check whether it is properly formed or not * * @param {String} id ID of the request or response * @param {String} to JID of the recipient * @param {String} type Type of the request ('set' for a request and 'result' for a response) * @param {Element} element The XMLElement to send */ sendXMLElement: function(id, to, type, element) { if (typeof element.tree === 'function') { element = element.tree(); } var iq = $iq({type: type, id: id, from: this._connection.jid, to: to}) .c("query", {xmlns: Strophe.NS.RPC}) .cnode(element); this._connection.send(iq.tree()); }, _sendForbiddenAccess: function(id, to) { var iq = $iq({type: "error", id: id, from: this._connection.jid, to: to}) .c("error", {code: 403, type: "auth"}) .c("forbidden", {xmlns: Strophe.NS.STANZAS}); this._connection.send(iq.tree()); }, /** * Add a request and response handlers * * @param {Function} request_h Request handler * @param {Function} response_h Response handler * @param {Object} context Context of the handlers */ addHandlers: function(request_h, response_h, context) { this.addRequestHandler(request_h, context); this.addResponseHandler(response_h, context); }, /** * Add a request handler * called every time a request is received * * @param {Function} handler The handler function * @param {Object} context Context of the handler */ addRequestHandler: function(handler, context) { this._connection.addHandler(this._filteredRequestHandler(handler, context), Strophe.NS.RPC, "iq", "set"); }, /** * Add a response handler * called every time a response is received * * @param {Function} handler The handler function called every time a response is received * @param {Object} context Context of the handler */ addResponseHandler: function(handler, context) { this._connection.addHandler(this._filteredResponseHandler(handler, context), Strophe.NS.RPC, "iq", "result"); }, /** * Add a raw XML handler for every RPC message incoming * @param {Function} handler The handler function called every time a rpc is received * @param {Object} context Context of the handler */ addXMLHandler: function(handler, context) { this._connection.addHandler(this._filteredHandler(handler, context), Strophe.NS.RPC, "iq"); }, _filter: function(xml) { var from = xml.getAttribute("from"); if (!this._jidInWhitelist(from)) { this._sendForbiddenAccess(xml.getAttribute("id"), from); return false; } return true; }, _filteredHandler: function(handler, context) { context = context || this; var self = this; return function(xml) { if (self._filter(xml)) { return handler.apply(context, arguments); } return true; }; }, _filteredResponseHandler: function(handler, context) { context = context || this; var self = this; return function(xml) { if (self._filter(xml)) { var rpc = self._parseResponseMessage(xml); if (rpc.result || rpc.result === null) return handler.call(context, rpc.id, rpc.from, rpc.result, false); else if (rpc.fault || rpc.fault === null) return handler.call(context, rpc.id, rpc.from, rpc.fault, true); } return true; }; }, _filteredRequestHandler: function(handler, context) { context = context || this; var self = this; return function(xml) { if (self._filter(xml)) { var rpc = self._parseRequestMessage(xml); return handler.call(context, rpc.id, rpc.from, rpc.method, rpc.params); } return true; }; }, /** * Helper to transform a Javascript object * into its XML representation as described in * the XML-RPC spec * * @param {Object} obj The object to convert * @return {Element} A <value> element */ _convertToXML: function(obj) { var key; var xml = new Strophe.Builder("value"); var regex = new RegExp("function (.*?)\\(\\) \\{.*"); var _ref, _ref1; var type = (_ref = regex.exec(typeof obj !== "undefined" && obj !== null ? (_ref1 = obj.constructor) != null ? _ref1.toString() : void 0 : void 0)) != null ? _ref[1] : void 0; switch (type) { case "Number": // Numbers can only be sent as integers or doubles. var number = (Math.floor(obj) !== obj) ? "double" : "i4"; xml.c(number).t(obj.toString()); break; case "String": xml.c("string").t(obj); break; case "Boolean": xml.c("boolean").t((obj*1).toString()); break; case "Object": xml.c("struct"); for (key in obj) { if (obj.hasOwnProperty(key) && typeof(obj[key]) !== "function") { xml.c("member").c("name").t(key); xml.up().cnode(this._convertToXML(obj[key])); xml.up().up(); } } break; case "Date": var datetext = obj.getFullYear() + this._padNumber(obj.getMonth()+1) + this._padNumber(obj.getDate()) + "T" + this._padNumber(obj.getHours()) + ":" + this._padNumber(obj.getMinutes()) + ":" + this._padNumber(obj.getSeconds()); xml.c("dateTime.iso8601").t(datetext); break; case "Array": xml.c("array").c("data"); for (key in obj) { if (obj.hasOwnProperty(key) && typeof(obj[key]) !== "function") { xml.cnode(this._convertToXML(obj[key])); xml.up(); } } break; default: break; } return xml.tree(); }, _padNumber: function(num) { if (num < 10) { num = "0" + num; } return num; }, _parseRequestMessage: function(iq) { var rpc = {}; rpc.from = iq.getAttribute("from"); rpc.id = iq.getAttribute("id") || null; // Method name var method = iq.getElementsByTagName("methodName")[0]; rpc.method = method ? Strophe.getText(method) : null; // Parameters rpc.params = null; try { var params = iq.getElementsByTagName("params")[0] .childNodes; if (params && params.length > 0) { rpc.params = []; for (var i = 0; i < params.length; i++) { rpc.params.push(this._convertFromXML(params[i].firstChild)); } } } catch(e) { rpc.params = null; } return rpc; }, _parseResponseMessage: function(iq) { var rpc = {}; rpc.from = iq.getAttribute("from"); rpc.id = iq.getAttribute("id") || null; try { var result = iq.getElementsByTagName("methodResponse")[0].firstChild; // Response var tag = result.tagName; if (tag === "params") { rpc.result = this._convertFromXML(result.firstChild.firstChild); } // Error else if (tag === "fault") { rpc.fault = this._convertFromXML(result.firstChild); } } catch(e) { rpc.result = null; } return rpc; }, _convertFromXML: function(obj) { if (!obj) return null; var data; var tag = obj.tagName.toLowerCase(); try { switch (tag) { case "value": return this._convertFromXML(obj.firstChild); case "double": case "i4": case "int": var number = Strophe.getText(obj); data = number * 1; break; case "boolean": var bool = Strophe.getText(obj); data = (bool === "1" || bool === "true") ? true : false; break; case "datetime.iso8601": var date = Strophe.getText(obj); data = new Date(); data.setFullYear(date.substring(0,4), date.substring(4,6) - 1, date.substring(6,8)); data.setHours(date.substring(9,11), date.substring(12,14), date.substring(15,17)); break; case "array": data = []; var datatag = obj.firstChild; for (var k = 0; k < datatag.childNodes.length; k++) { var value = datatag.childNodes[k]; data.push(this._convertFromXML(value.firstChild)); } break; case "struct": data = {}; for (var j = 0; j < obj.childNodes.length; j++) { var membername = Strophe.getText(obj.childNodes[j].getElementsByTagName("name")[0]); var membervalue = obj.childNodes[j].getElementsByTagName("value")[0].firstChild; data[membername] = membervalue ? this._convertFromXML(membervalue) : null; } break; case "string": data = Strophe.getText(obj); break; default: data = null; break; } } catch(e) { data = null; } return data; } });