Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #44 from jinroh/master

Jabber-RPC module with parsing
  • Loading branch information...
commit ae7c1dcdd5da71258cd86a5a640973d7cdaf8fbe 2 parents 0a4be9f + 4474c58
@flosse flosse authored
View
25 rpc/README.md
@@ -39,7 +39,7 @@ The plugin prototype is accessible from the `connection.rpc` variable
### Send an RPC
-There are three functions to send RPC :
+There are four functions to send RPC :
- `sendRequest(id, to, method, params)`
- `method` is the string name of the method to call
@@ -49,6 +49,8 @@ There are three functions to send RPC :
- `sendError(id, to, code, message)`
- `code` is the number of the error
- `message` is the message describing the error
+- `sendXMLElement(id, to, xml)`
+ - `xml` is the XML Element that will be sent (whether it is properly formed or not)
The parameters `id` and `to` are respectively the id of the request and the the jid of the recipient.
@@ -58,8 +60,27 @@ It is possible to handle incoming RPCs using the functions :
- `addRequestHandler`
- `addResponseHandler`
+- `addXMLHandler`
-Both these functions take a handler function as parameter.
+The handlers you pass to these functions take different parameters :
+
+```javascript
+var responseHandler = function(id, from, result, error) {
+ // error is a boolean
+ // it is true if the response was an error message
+ if (error === true) { ... }
+ else { ... }
+}
+connection.rpc.addResponseHandler(responseHandler);
+
+var requestHandler = function(id, from, method, parameters) { ... }
+connection.rpc.addRequestHandler(requestHandler);
+
+var xmlHandler = function(xml) { ... }
+connection.rpc.addXMLHandler(xmlHandler)
+```
+
+Note that the parser of this module won't throw any exception. However `result`, `method` and `parameters` will be set as `null` if the incoming message is not XML-RPC compliant.
### Whitelist
View
1  rpc/spec/strophe.rpc.spec.helper.js
@@ -1,7 +1,6 @@
var helper = (function() {
function receive(c,req) {
c._dataRecv(createRequest(req));
- expect(c.send).toHaveBeenCalled();
}
function spyon (obj, method, cb) {
View
75 rpc/spec/strophe.rpc.spec.js
@@ -3,6 +3,8 @@ var mockConnection = helper.mockConnection,
describe("Strophe Jabber-RPC plugin", function() {
+ var connection, rpc;
+
beforeEach(function() {
connection = mockConnection();
rpc = connection.rpc;
@@ -154,15 +156,74 @@ describe("Strophe Jabber-RPC plugin", function() {
rpc.sendError("id231", "foo@bar/baz", -30021 ,"error message");
});
+ describe("Handlers", function() {
+
+ var handler;
+
+ beforeEach(function() {
+ handler = jasmine.createSpy();
+ });
+
+ it("should be possible to add a request handler", function() {
+ rpc.addRequestHandler(handler);
+ var iq = $iq({type: "set", id: "123", from: "foo@bar", to: connection.jid})
+ .c("query", {xmlns: Strophe.NS.RPC})
+ .c("methodCall")
+ .c("methodName").t("pong")
+ .up()
+ .c("params")
+ .c("param")
+ .c("value")
+ .c("string").t("foo")
+ .up().up().up()
+ .c("param")
+ .c("value")
+ .c("i4").t("-32");
+ receive(connection, iq);
+ expect(handler).toHaveBeenCalledWith("123", "foo@bar", "pong", ["foo", -32]);
+ });
+
+ it("should be possible to add a response handler", function() {
+ rpc.addResponseHandler(handler);
+ iq = $iq({ type: "result", id: "id123", from: "foo@bar/baz", to: connection.jid })
+ .c("query", {xmlns: Strophe.NS.RPC})
+ .c("methodResponse")
+ .c("params")
+ .c("param")
+ .c("value")
+ .c("string").t("foo");
+ receive(connection, iq);
+ expect(handler).toHaveBeenCalledWith("id123", "foo@bar/baz", "foo", false);
+ });
+
+ it("should be possible to parse a fault response", function() {
+ rpc.addResponseHandler(handler);
+ var iq =$iq({type: "result", id: "123", from: "foo@bar", to: connection.jid})
+ .c("query", {xmlns: Strophe.NS.RPC})
+ .c("methodResponse")
+ .c("fault")
+ .c("value")
+ .c("struct")
+ .c("member")
+ .c("name").t("faultString")
+ .up()
+ .c("value")
+ .c("string").t("parsererror");
+ receive(connection, iq);
+ expect(handler).toHaveBeenCalledWith("123", "foo@bar", {faultString:"parsererror"}, true)
+ });
+
+ });
+
describe("Forbidden access", function() {
beforeEach(function() {
rpc.addJidToWhiteList(["*@jabber.org", "*@localhost"]);
- var handler = function() {};
- rpc.addHandlers(handler, handler);
});
it("should send forbidden access to the wrong nodes", function() {
+ var handler = function() {};
+ rpc.addHandlers(handler, handler);
spyon(connection, "send", function(iq) {
expect(iq.getAttribute("type")).toEqual("error");
expect(iq.getAttribute("id")).toEqual("123");
@@ -179,21 +240,21 @@ describe("Strophe Jabber-RPC plugin", function() {
expect(forbidden.getAttribute("xmlns")).toEqual(Strophe.NS.STANZAS);
});
- iq = $iq({type: "set", id: "123", from: "foo@bar", to: connection.jid})
+ var iq = $iq({type: "set", id: "123", from: "foo@bar", to: connection.jid})
.c("query", {xmlns: Strophe.NS.RPC});
receive(connection, iq);
});
it("should NOT send forbidden access to the right nodes", function() {
- spyOn(connection, 'send');
- expect(connection.send).not.toHaveBeenCalled();
- iq = $iq({type: "set", id: "123", from: "foo@bar", to: connection.jid})
+ spyOn(connection, "send");
+ var iq = $iq({type: "set", id: "123", from: "foo@jabber.org", to: connection.jid})
.c("query", {xmlns: Strophe.NS.RPC});
-
receive(connection, iq);
+ expect(connection.send).not.toHaveBeenCalled();
});
});
+
});
});
View
164 rpc/strophe.rpc.js
@@ -218,7 +218,7 @@ Strophe.addConnectionPlugin("rpc", {
* @param {Object} context Context of the handler
*/
addRequestHandler: function(handler, context) {
- this._connection.addHandler(this._filteredHandler(handler, context), Strophe.NS.RPC, "iq", "set");
+ this._connection.addHandler(this._filteredRequestHandler(handler, context), Strophe.NS.RPC, "iq", "set");
},
/**
@@ -229,19 +229,62 @@ Strophe.addConnectionPlugin("rpc", {
* @param {Object} context Context of the handler
*/
addResponseHandler: function(handler, context) {
- this._connection.addHandler(this._filteredHandler(handler, context), Strophe.NS.RPC, "iq", "result");
+ 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(doc) {
- var from = doc.getAttribute("from");
- if (!self._jidInWhitelist(from)) {
- self._sendForbiddenAccess(doc.getAttribute("id"), from);
- return true;
+ return function(xml) {
+ if (self._filter(xml)) {
+ return handler.apply(context, arguments);
}
- 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;
};
},
@@ -311,6 +354,111 @@ Strophe.addConnectionPlugin("rpc", {
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 ? method.textContent : 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 = obj.textContent;
+ data = number * 1;
+ break;
+ case "boolean":
+ var bool = obj.textContent;
+ data = (bool === "1" || bool === "true") ? true : false;
+ break;
+ case "datetime.iso8601":
+ var date = obj.textContent;
+ 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 = obj.childNodes[j].getElementsByTagName("name")[0].textContent;
+ var membervalue = obj.childNodes[j].getElementsByTagName("value")[0].firstChild;
+ data[membername] = membervalue ? this._convertFromXML(membervalue) : null;
+ }
+ break;
+ case "string":
+ data = obj.textContent;
+ break;
+ default:
+ data = null;
+ break;
+ }
+ } catch(e) {
+ data = null;
+ }
+ return data;
}
});
Please sign in to comment.
Something went wrong with that request. Please try again.