|
|
|
@ -1,9 +1,8 @@
@@ -1,9 +1,8 @@
|
|
|
|
|
/** |
|
|
|
|
* This plugin requires JSON to be available |
|
|
|
|
* |
|
|
|
|
* The plan is to make use of websockets if they are available, but work just as well with only |
|
|
|
|
* http if not. |
|
|
|
|
* JsonRpcClient |
|
|
|
|
* |
|
|
|
|
* A JSON RPC Client that uses WebSockets if available otherwise fallbacks to ajax. |
|
|
|
|
* Depends on JSON, if browser lacks native support either use JSON3 or jquery.json. |
|
|
|
|
* Usage example: |
|
|
|
|
* |
|
|
|
|
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' }); |
|
|
|
@ -16,108 +15,143 @@
@@ -16,108 +15,143 @@
|
|
|
|
|
* More examples are available in README.md |
|
|
|
|
*/ |
|
|
|
|
(function($) { |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @fn new |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
* |
|
|
|
|
* @param options An object stating the backends: |
|
|
|
|
* @param {object} options An object stating the backends: |
|
|
|
|
* ajaxUrl A url (relative or absolute) to a http(s) backend. |
|
|
|
|
* headers An object that will be passed along to $.ajax in options.headers |
|
|
|
|
* xhrFields An object that will be passed along to $.ajax in options.xhrFields |
|
|
|
|
* socketUrl A url (relative of absolute) to a ws(s) backend. |
|
|
|
|
* onmessage A socket message handler for other messages (non-responses). |
|
|
|
|
* onopen A socket onopen handler. (Not used for custom getSocket.) |
|
|
|
|
* onclose A socket onclose handler. (Not used for custom getSocket.) |
|
|
|
|
* onerror A socket onerror handler. (Not used for custom getSocket.) |
|
|
|
|
* getSocket A function returning a WebSocket or null. |
|
|
|
|
* It must take an onmessage_cb and bind it to the onmessage event |
|
|
|
|
* (or chain it before/after some other onmessage handler). |
|
|
|
|
* Or, it could return null if no socket is available. |
|
|
|
|
* The returned instance must have readyState <= 1, and if less than 1, |
|
|
|
|
* react to onopen binding. |
|
|
|
|
* timeout (optional) A number of ms to wait before timing out and failing a |
|
|
|
|
* call. If specified a setTimeout will be used to keep track of calls |
|
|
|
|
* made through a websocket. |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient = function(options) { |
|
|
|
|
var JsonRpcClient = function(options) { |
|
|
|
|
var self = this; |
|
|
|
|
var noop = function() {}; |
|
|
|
|
this.options = $.extend({ |
|
|
|
|
ajaxUrl : null, |
|
|
|
|
socketUrl : null, ///< The ws-url for default getSocket.
|
|
|
|
|
onmessage : null, ///< Other onmessage-handler.
|
|
|
|
|
getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); } |
|
|
|
|
headers : {}, ///< Optional additional headers to send in $.ajax request.
|
|
|
|
|
socketUrl : null, ///< WebSocket URL. (Not used if a custom getSocket is supplied.)
|
|
|
|
|
onmessage : noop, ///< Optional onmessage-handler for WebSocket.
|
|
|
|
|
onopen : noop, ///< Optional onopen-handler for WebSocket.
|
|
|
|
|
onclose : noop, ///< Optional onclose-handler for WebSocket.
|
|
|
|
|
onerror : noop, ///< Optional onerror-handler for WebSocket.
|
|
|
|
|
/// Custom socket supplier for using an already existing socket
|
|
|
|
|
getSocket : function(onmessageCb) { return self._getSocket(onmessageCb); } |
|
|
|
|
}, options); |
|
|
|
|
|
|
|
|
|
// Declare an instance version of the onmessage callback to wrap 'this'.
|
|
|
|
|
this.wsOnMessage = function(event) { self._wsOnMessage(event); }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/// Holding the WebSocket on default getsocket.
|
|
|
|
|
$.JsonRpcClient.prototype._ws_socket = null; |
|
|
|
|
this._wsSocket = null; |
|
|
|
|
|
|
|
|
|
/// Object <id>: { success_cb: cb, error_cb: cb }
|
|
|
|
|
$.JsonRpcClient.prototype._ws_callbacks = {}; |
|
|
|
|
this._wsCallbacks = {}; |
|
|
|
|
|
|
|
|
|
/// The next JSON-RPC request id.
|
|
|
|
|
$.JsonRpcClient.prototype._current_id = 1; |
|
|
|
|
this._currentId = 1; |
|
|
|
|
|
|
|
|
|
//queue for ws request sent *before* ws is open.
|
|
|
|
|
this._wsRequestQueue = []; |
|
|
|
|
|
|
|
|
|
if (!window.JSON && $ && $.toJSON) { |
|
|
|
|
this.JSON = { |
|
|
|
|
stringify: $.toJSON, |
|
|
|
|
parse: $.parseJSON |
|
|
|
|
}; |
|
|
|
|
} else { |
|
|
|
|
this.JSON = JSON; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @fn call |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
* |
|
|
|
|
* @param method The method to run on JSON-RPC server. |
|
|
|
|
* @param params The params; an array or object. |
|
|
|
|
* @param success_cb A callback for successful request. |
|
|
|
|
* @param error_cb A callback for error. |
|
|
|
|
* @param {string} method The method to run on JSON-RPC server. |
|
|
|
|
* @param {object|array} params The params; an array or object. |
|
|
|
|
* @param {function} successCb A callback for successful request. |
|
|
|
|
* @param {function} errorCb A callback for error. |
|
|
|
|
* |
|
|
|
|
* @return {object} Returns the deferred object that $.ajax returns or {null} for websockets |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) { |
|
|
|
|
JsonRpcClient.prototype.call = function(method, params, successCb, errorCb) { |
|
|
|
|
successCb = typeof successCb === 'function' ? successCb : function() {}; |
|
|
|
|
errorCb = typeof errorCb === 'function' ? errorCb : function() {}; |
|
|
|
|
|
|
|
|
|
// Construct the JSON-RPC 2.0 request.
|
|
|
|
|
var request = { |
|
|
|
|
jsonrpc : '2.0', |
|
|
|
|
method : method, |
|
|
|
|
params : params, |
|
|
|
|
id : this._current_id++ // Increase the id counter to match request/response
|
|
|
|
|
id : this._currentId++ // Increase the id counter to match request/response
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Try making a WebSocket call.
|
|
|
|
|
var socket = this.options.getSocket(this.wsOnMessage); |
|
|
|
|
if (socket !== null) { |
|
|
|
|
this._wsCall(socket, request, success_cb, error_cb); |
|
|
|
|
return; |
|
|
|
|
this._wsCall(socket, request, successCb, errorCb); |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// No WebSocket, and no HTTP backend? This won't work.
|
|
|
|
|
if (this.options.ajaxUrl === null) { |
|
|
|
|
throw "$.JsonRpcClient.call used with no websocket and no http endpoint."; |
|
|
|
|
throw 'JsonRpcClient.call used with no websocket and no http endpoint.'; |
|
|
|
|
} |
|
|
|
|
var options = this.options; |
|
|
|
|
|
|
|
|
|
$.ajax({ |
|
|
|
|
var self = this; |
|
|
|
|
|
|
|
|
|
var deferred = $.ajax({ |
|
|
|
|
type : 'POST', |
|
|
|
|
url : this.options.ajaxUrl, |
|
|
|
|
data : JSON.stringify(request), |
|
|
|
|
contentType: 'application/json', |
|
|
|
|
data : this.JSON.stringify(request), |
|
|
|
|
dataType : 'json', |
|
|
|
|
contentType: "application/json; charset=utf-8", |
|
|
|
|
cache : false, |
|
|
|
|
beforeSend: function (xhr) { |
|
|
|
|
if( options.username != null && options.username != undefined ) { |
|
|
|
|
xhr.setRequestHeader('Authorization', "Basic " + btoa(options.username + ":" + options.password)); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
headers : this.options.headers, |
|
|
|
|
xhrFields : this.options.xhrFields, |
|
|
|
|
timeout : this.options.timeout, |
|
|
|
|
|
|
|
|
|
success : function(data) { |
|
|
|
|
if ('error' in data && data.error !== null) { |
|
|
|
|
errorCb(data.error); |
|
|
|
|
} else { |
|
|
|
|
success_cb(data.result); |
|
|
|
|
successCb(data.result); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// JSON-RPC Server could return non-200 on error
|
|
|
|
|
error : function(jqXHR, textStatus, errorThrown) { |
|
|
|
|
try { |
|
|
|
|
var response = JSON.parse(jqXHR.responseText); |
|
|
|
|
if ('console' in window) console.log(response); |
|
|
|
|
error_cb(response.error); |
|
|
|
|
var response = self.JSON.parse(jqXHR.responseText); |
|
|
|
|
if ('console' in window) { console.log(response); } |
|
|
|
|
|
|
|
|
|
errorCb(response.error); |
|
|
|
|
} |
|
|
|
|
catch (err) { |
|
|
|
|
// Perhaps the responseText wasn't really a jsonrpc-error.
|
|
|
|
|
error_cb({ error: jqXHR.responseText }); |
|
|
|
|
errorCb({error: jqXHR.responseText}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return deferred; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -127,12 +161,14 @@
@@ -127,12 +161,14 @@
|
|
|
|
|
* This is very similar to call, but has no id and no handling of callbacks. |
|
|
|
|
* |
|
|
|
|
* @fn notify |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
* |
|
|
|
|
* @param {string} method The method to run on JSON-RPC server. |
|
|
|
|
* @param {object|array} params The params; an array or object. |
|
|
|
|
* |
|
|
|
|
* @param method The method to run on JSON-RPC server. |
|
|
|
|
* @param params The params; an array or object. |
|
|
|
|
* @return {object} Returns the deferred object that $.ajax returns or {null} for websockets |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype.notify = function(method, params) { |
|
|
|
|
JsonRpcClient.prototype.notify = function(method, params) { |
|
|
|
|
// Construct the JSON-RPC 2.0 request.
|
|
|
|
|
var request = { |
|
|
|
|
jsonrpc: '2.0', |
|
|
|
@ -144,47 +180,46 @@
@@ -144,47 +180,46 @@
|
|
|
|
|
var socket = this.options.getSocket(this.wsOnMessage); |
|
|
|
|
if (socket !== null) { |
|
|
|
|
this._wsCall(socket, request); |
|
|
|
|
return; |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// No WebSocket, and no HTTP backend? This won't work.
|
|
|
|
|
if (this.options.ajaxUrl === null) { |
|
|
|
|
throw "$.JsonRpcClient.notify used with no websocket and no http endpoint."; |
|
|
|
|
throw 'JsonRpcClient.notify used with no websocket and no http endpoint.'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$.ajax({ |
|
|
|
|
var deferred = $.ajax({ |
|
|
|
|
type : 'POST', |
|
|
|
|
url : this.options.ajaxUrl, |
|
|
|
|
data : JSON.stringify(request), |
|
|
|
|
contentType: 'application/json', |
|
|
|
|
data : this.JSON.stringify(request), |
|
|
|
|
dataType : 'json', |
|
|
|
|
contentType: "application/json; charset=utf-8", |
|
|
|
|
cache : false, |
|
|
|
|
beforeSend: function (xhr){ |
|
|
|
|
if( options.username != null && options.username != undefined ) { |
|
|
|
|
xhr.setRequestHeader('Authorization', "Basic " + btoa(options.username + ":" + options.password)); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
headers : this.options.headers, |
|
|
|
|
xhrFields : this.options.xhrFields |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return deferred; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Make a batch-call by using a callback. |
|
|
|
|
* |
|
|
|
|
* The callback will get an object "batch" as only argument. On batch, you can call the methods |
|
|
|
|
* "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be |
|
|
|
|
* "call" and "notify" just as if it was a normal JsonRpcClient object, and all calls will be |
|
|
|
|
* sent as a batch call then the callback is done. |
|
|
|
|
* |
|
|
|
|
* @fn batch |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
* |
|
|
|
|
* @param callback The main function which will get a batch handler to run call and notify on. |
|
|
|
|
* @param all_done_cb A callback function to call after all results have been handled. |
|
|
|
|
* @param error_cb A callback function to call if there is an error from the server. |
|
|
|
|
* @param {function} callback This function will get a batch handler to run call and notify on. |
|
|
|
|
* @param {function} allDoneCb A callback function to call after all results have been handled. |
|
|
|
|
* @param {function} errorCb A callback function to call if there is an error from the server. |
|
|
|
|
* Note, that batch calls should always get an overall success, and the |
|
|
|
|
* only error |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) { |
|
|
|
|
var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb); |
|
|
|
|
JsonRpcClient.prototype.batch = function(callback, allDoneCb, errorCb) { |
|
|
|
|
var batch = new JsonRpcClient._batchObject(this, allDoneCb, errorCb); |
|
|
|
|
callback(batch); |
|
|
|
|
batch._execute(); |
|
|
|
|
}; |
|
|
|
@ -192,54 +227,92 @@
@@ -192,54 +227,92 @@
|
|
|
|
|
/** |
|
|
|
|
* The default getSocket handler. |
|
|
|
|
* |
|
|
|
|
* @param onmessage_cb The callback to be bound to onmessage events on the socket. |
|
|
|
|
* @param {function} onmessageCb The callback to be bound to onmessage events on the socket. |
|
|
|
|
* |
|
|
|
|
* @fn _getSocket |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) { |
|
|
|
|
JsonRpcClient.prototype._getSocket = function(onmessageCb) { |
|
|
|
|
// If there is no ws url set, we don't have a socket.
|
|
|
|
|
// Likewise, if there is no window.WebSocket.
|
|
|
|
|
if (this.options.socketUrl === null || !("WebSocket" in window)) return null; |
|
|
|
|
if (this.options.socketUrl === null || !('WebSocket' in window)) { return null; } |
|
|
|
|
|
|
|
|
|
if (this._wsSocket === null || this._wsSocket.readyState > 1) { |
|
|
|
|
|
|
|
|
|
if (this._ws_socket === null || this._ws_socket.readyState > 1) { |
|
|
|
|
try { |
|
|
|
|
// No socket, or dying socket, let's get a new one.
|
|
|
|
|
this._ws_socket = new WebSocket(this.options.socketUrl); |
|
|
|
|
this._wsSocket = new WebSocket(this.options.socketUrl); |
|
|
|
|
} catch (e) { |
|
|
|
|
// This can happen if the server is down, or malconfigured.
|
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Set up onmessage handler.
|
|
|
|
|
this._ws_socket.onmessage = onmessage_cb; |
|
|
|
|
this._wsSocket.onmessage = onmessageCb; |
|
|
|
|
|
|
|
|
|
var that = this; |
|
|
|
|
// Set up onclose handler.
|
|
|
|
|
this._wsSocket.onclose = function(ev) { that._wsOnClose(ev); }; |
|
|
|
|
|
|
|
|
|
// Set up onerror handler.
|
|
|
|
|
this._wsSocket.onerror = function(ev) { that._wsOnError(ev); }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return this._ws_socket; |
|
|
|
|
return this._wsSocket; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Internal handler to dispatch a JRON-RPC request through a websocket. |
|
|
|
|
* |
|
|
|
|
* @fn _wsCall |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) { |
|
|
|
|
var request_json = JSON.stringify(request); |
|
|
|
|
JsonRpcClient.prototype._wsCall = function(socket, request, successCb, errorCb) { |
|
|
|
|
var requestJson = this.JSON.stringify(request); |
|
|
|
|
|
|
|
|
|
// Setup callbacks. If there is an id, this is a call and not a notify.
|
|
|
|
|
if ('id' in request && typeof successCb !== 'undefined') { |
|
|
|
|
this._wsCallbacks[request.id] = {successCb: successCb, errorCb: errorCb}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (socket.readyState < 1) { |
|
|
|
|
|
|
|
|
|
// Queue request
|
|
|
|
|
this._wsRequestQueue.push(requestJson); |
|
|
|
|
|
|
|
|
|
if (!socket.onopen) { |
|
|
|
|
// The websocket is not open yet; we have to set sending of the message in onopen.
|
|
|
|
|
self = this; // In closure below, this is set to the WebSocket. Use self instead.
|
|
|
|
|
var self = this; // In closure below, this is set to the WebSocket. Use self instead.
|
|
|
|
|
|
|
|
|
|
// Set up sending of message for when the socket is open.
|
|
|
|
|
socket.onopen = function() { |
|
|
|
|
// Send the request.
|
|
|
|
|
socket.send(request_json); |
|
|
|
|
socket.onopen = function(event) { |
|
|
|
|
// Hook for extra onopen callback
|
|
|
|
|
self.options.onopen(event); |
|
|
|
|
|
|
|
|
|
// Send queued requests.
|
|
|
|
|
var timeout = self.options.timeout; |
|
|
|
|
var request; |
|
|
|
|
for (var i = 0; i < self._wsRequestQueue.length; i++) { |
|
|
|
|
request = self._wsRequestQueue[i]; |
|
|
|
|
|
|
|
|
|
// Do we use timeouts, and if so, is it a call?
|
|
|
|
|
if (timeout && self._wsCallbacks[request.id]) { |
|
|
|
|
self._wsCallbacks[request.id].timeout = self._createTimeout(request.id); |
|
|
|
|
} |
|
|
|
|
socket.send(request); |
|
|
|
|
} |
|
|
|
|
self._wsRequestQueue = []; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
// We have a socket and it should be ready to send on.
|
|
|
|
|
socket.send(request_json); |
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
// Do we use timeouts, and if so, is it a call?
|
|
|
|
|
if (this.options.timeout && this._wsCallbacks[request.id]) { |
|
|
|
|
this._wsCallbacks[request.id].timeout = this._createTimeout(request.id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Setup callbacks. If there is an id, this is a call and not a notify.
|
|
|
|
|
if ('id' in request && typeof success_cb !== 'undefined') { |
|
|
|
|
this._ws_callbacks[request.id] = { success_cb: success_cb, error_cb: error_cb }; |
|
|
|
|
// We have a socket and it should be ready to send on.
|
|
|
|
|
socket.send(requestJson); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -248,59 +321,111 @@
@@ -248,59 +321,111 @@
|
|
|
|
|
* response, and if so, tries to couple it with a given callback. Otherwise, it falls back to |
|
|
|
|
* given external onmessage-handler, if any. |
|
|
|
|
* |
|
|
|
|
* @param event The websocket onmessage-event. |
|
|
|
|
* @param {event} event The websocket onmessage-event. |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient.prototype._wsOnMessage = function(event) { |
|
|
|
|
JsonRpcClient.prototype._wsOnMessage = function(event) { |
|
|
|
|
|
|
|
|
|
// Check if this could be a JSON RPC message.
|
|
|
|
|
var response; |
|
|
|
|
try { |
|
|
|
|
var response = JSON.parse(event.data); |
|
|
|
|
response = this.JSON.parse(event.data); |
|
|
|
|
} catch (err) { |
|
|
|
|
this.options.onmessage(event); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
|
|
|
|
|
|
|
|
|
|
if (typeof response === 'object' |
|
|
|
|
&& 'jsonrpc' in response |
|
|
|
|
&& response.jsonrpc === '2.0') { |
|
|
|
|
if (typeof response === 'object' && response.jsonrpc === '2.0') { |
|
|
|
|
|
|
|
|
|
/// @todo Handle bad response (without id).
|
|
|
|
|
|
|
|
|
|
// If this is an object with result, it is a response.
|
|
|
|
|
if ('result' in response && this._ws_callbacks[response.id]) { |
|
|
|
|
if ('result' in response && this._wsCallbacks[response.id]) { |
|
|
|
|
// Get the success callback.
|
|
|
|
|
var success_cb = this._ws_callbacks[response.id].success_cb; |
|
|
|
|
var successCb = this._wsCallbacks[response.id].successCb; |
|
|
|
|
|
|
|
|
|
// Clear any timeout
|
|
|
|
|
if (this._wsCallbacks[response.id].timeout) { |
|
|
|
|
clearTimeout(this._wsCallbacks[response.id].timeout); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Delete the callback from the storage.
|
|
|
|
|
delete this._ws_callbacks[response.id]; |
|
|
|
|
delete this._wsCallbacks[response.id]; |
|
|
|
|
|
|
|
|
|
// Run callback with result as parameter.
|
|
|
|
|
success_cb(response.result); |
|
|
|
|
successCb(response.result); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If this is an object with error, it is an error response.
|
|
|
|
|
else if ('error' in response && this._ws_callbacks[response.id]) { |
|
|
|
|
else if ('error' in response && response.error !== null && this._wsCallbacks[response.id]) { |
|
|
|
|
// Get the error callback.
|
|
|
|
|
var error_cb = this._ws_callbacks[response.id].error_cb; |
|
|
|
|
var errorCb = this._wsCallbacks[response.id].errorCb; |
|
|
|
|
|
|
|
|
|
// Delete the callback from the storage.
|
|
|
|
|
delete this._ws_callbacks[response.id]; |
|
|
|
|
delete this._wsCallbacks[response.id]; |
|
|
|
|
|
|
|
|
|
// Run callback with the error object as parameter.
|
|
|
|
|
error_cb(response.error); |
|
|
|
|
errorCb(response.error); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
catch (err) { |
|
|
|
|
// Probably an error while parsing a non json-string as json. All real JSON-RPC cases are
|
|
|
|
|
// handled above, and the fallback method is called below.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// This is not a JSON-RPC response. Call the fallback message handler, if given.
|
|
|
|
|
if (typeof this.options.onmessage === 'function') { |
|
|
|
|
// If we get here it's an invalid JSON-RPC response, pass to fallback message handler.
|
|
|
|
|
this.options.onmessage(event); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Internal WebSocket error handler. |
|
|
|
|
* Will execute all unresolved calls immideatly. |
|
|
|
|
**/ |
|
|
|
|
JsonRpcClient.prototype._wsOnError = function(event) { |
|
|
|
|
this._failAllCalls('Socket errored.'); |
|
|
|
|
this.options.onerror(event); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Internal WebSocket close handler. |
|
|
|
|
* Will execute all unresolved calls immideatly. |
|
|
|
|
**/ |
|
|
|
|
JsonRpcClient.prototype._wsOnClose = function(event) { |
|
|
|
|
this._failAllCalls('Socket closed.'); |
|
|
|
|
this.options.onclose(event); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Execute error handler on all pending calls. |
|
|
|
|
*/ |
|
|
|
|
JsonRpcClient.prototype._failAllCalls = function(error) { |
|
|
|
|
for (var key in this._wsCallbacks) { |
|
|
|
|
if (this._wsCallbacks.hasOwnProperty(key)) { |
|
|
|
|
// Get the error callback.
|
|
|
|
|
var errorCb = this._wsCallbacks[key].errorCb; |
|
|
|
|
|
|
|
|
|
// Run callback with the error object as parameter.
|
|
|
|
|
errorCb(error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Throw 'em away
|
|
|
|
|
this._wsCallbacks = {}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Create a timeout for this request |
|
|
|
|
*/ |
|
|
|
|
JsonRpcClient.prototype._createTimeout = function(id) { |
|
|
|
|
if (this.options.timeout) { |
|
|
|
|
var that = this; |
|
|
|
|
return setTimeout(function() { |
|
|
|
|
if (that._wsCallbacks[id]) { |
|
|
|
|
var errorCb = that._wsCallbacks[id].errorCb; |
|
|
|
|
delete that._wsCallbacks[id]; |
|
|
|
|
errorCb('Call timed out.'); |
|
|
|
|
} |
|
|
|
|
}, this.options.timeout); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/************************************************************************************************ |
|
|
|
|
* Batch object with methods |
|
|
|
@ -309,37 +434,36 @@
@@ -309,37 +434,36 @@
|
|
|
|
|
/** |
|
|
|
|
* Handling object for batch calls. |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) { |
|
|
|
|
JsonRpcClient._batchObject = function(jsonrpcclient, allDoneCb, errorCb) { |
|
|
|
|
// Array of objects to hold the call and notify requests. Each objects will have the request
|
|
|
|
|
// object, and unless it is a notify, success_cb and error_cb.
|
|
|
|
|
// object, and unless it is a notify, successCb and errorCb.
|
|
|
|
|
this._requests = []; |
|
|
|
|
|
|
|
|
|
this.jsonrpcclient = jsonrpcclient; |
|
|
|
|
this.all_done_cb = all_done_cb; |
|
|
|
|
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {}; |
|
|
|
|
|
|
|
|
|
this.allDoneCb = allDoneCb; |
|
|
|
|
this.errorCb = typeof errorCb === 'function' ? errorCb : function() {}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @sa $.JsonRpcClient.prototype.call |
|
|
|
|
* @sa JsonRpcClient.prototype.call |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) { |
|
|
|
|
JsonRpcClient._batchObject.prototype.call = function(method, params, successCb, errorCb) { |
|
|
|
|
this._requests.push({ |
|
|
|
|
request : { |
|
|
|
|
jsonrpc : '2.0', |
|
|
|
|
method : method, |
|
|
|
|
params : params, |
|
|
|
|
id : this.jsonrpcclient._current_id++ // Use the client's id series.
|
|
|
|
|
id : this.jsonrpcclient._currentId++ // Use the client's id series.
|
|
|
|
|
}, |
|
|
|
|
success_cb : success_cb, |
|
|
|
|
error_cb : error_cb |
|
|
|
|
successCb : successCb, |
|
|
|
|
errorCb : errorCb |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @sa $.JsonRpcClient.prototype.notify |
|
|
|
|
* @sa JsonRpcClient.prototype.notify |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) { |
|
|
|
|
JsonRpcClient._batchObject.prototype.notify = function(method, params) { |
|
|
|
|
this._requests.push({ |
|
|
|
|
request : { |
|
|
|
|
jsonrpc : '2.0', |
|
|
|
@ -351,92 +475,147 @@
@@ -351,92 +475,147 @@
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Executes the batched up calls. |
|
|
|
|
* |
|
|
|
|
* @return {object} Returns the deferred object that $.ajax returns or {null} for websockets |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient._batchObject.prototype._execute = function() { |
|
|
|
|
JsonRpcClient._batchObject.prototype._execute = function() { |
|
|
|
|
var self = this; |
|
|
|
|
var deferred = null; // Used to store and return the deffered that $.ajax returns
|
|
|
|
|
|
|
|
|
|
if (this._requests.length === 0) return; // All done :P
|
|
|
|
|
if (this._requests.length === 0) { return; } // All done :P
|
|
|
|
|
|
|
|
|
|
// Collect all request data and sort handlers by request id.
|
|
|
|
|
var batch_request = []; |
|
|
|
|
var handlers = {}; |
|
|
|
|
var batchRequest = []; |
|
|
|
|
|
|
|
|
|
// If we have a WebSocket, just send the requests individually like normal calls.
|
|
|
|
|
var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage); |
|
|
|
|
|
|
|
|
|
if (socket !== null) { |
|
|
|
|
// We need to keep track of results for the all done callback
|
|
|
|
|
var expectedNrOfCb = 0; |
|
|
|
|
var cbResults = []; |
|
|
|
|
|
|
|
|
|
var wrapCb = function(cb) { |
|
|
|
|
if (!self.allDoneCb) { // No all done callback? no need to keep track
|
|
|
|
|
return cb; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return function(data) { |
|
|
|
|
cb(data); |
|
|
|
|
cbResults.push(data); |
|
|
|
|
expectedNrOfCb--; |
|
|
|
|
if (expectedNrOfCb <= 0) { |
|
|
|
|
// Change order so that it maps to request order
|
|
|
|
|
var i; |
|
|
|
|
var resultMap = {}; |
|
|
|
|
for (i = 0; i < cbResults.length; i++) { |
|
|
|
|
resultMap[cbResults[i].id] = cbResults[i]; |
|
|
|
|
} |
|
|
|
|
var results = []; |
|
|
|
|
for (i = 0; i < self._requests.length; i++) { |
|
|
|
|
if (resultMap[self._requests[i].id]) { |
|
|
|
|
results.push(resultMap[self._requests[i].id]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Call all done!
|
|
|
|
|
self.allDoneCb(results); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
for (var i = 0; i < this._requests.length; i++) { |
|
|
|
|
var call = this._requests[i]; |
|
|
|
|
var success_cb = ('success_cb' in call) ? call.success_cb : undefined; |
|
|
|
|
var error_cb = ('error_cb' in call) ? call.error_cb : undefined; |
|
|
|
|
this._wsCall(socket, call.request, success_cb, error_cb); |
|
|
|
|
|
|
|
|
|
if ('id' in call.request) { |
|
|
|
|
// We expect an answer
|
|
|
|
|
expectedNrOfCb++; |
|
|
|
|
} |
|
|
|
|
if (typeof all_done_cb === 'function') all_done_cb(result); |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
self.jsonrpcclient._wsCall( |
|
|
|
|
socket, call.request, wrapCb(call.successCb), wrapCb(call.errorCb) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
} else { |
|
|
|
|
// No websocket, let's use ajax
|
|
|
|
|
var handlers = {}; |
|
|
|
|
|
|
|
|
|
for (var i = 0; i < this._requests.length; i++) { |
|
|
|
|
var call = this._requests[i]; |
|
|
|
|
batch_request.push(call.request); |
|
|
|
|
batchRequest.push(call.request); |
|
|
|
|
|
|
|
|
|
// If the request has an id, it should handle returns (otherwise it's a notify).
|
|
|
|
|
if ('id' in call.request) { |
|
|
|
|
handlers[call.request.id] = { |
|
|
|
|
success_cb : call.success_cb, |
|
|
|
|
error_cb : call.error_cb |
|
|
|
|
successCb : call.successCb, |
|
|
|
|
errorCb : call.errorCb |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); }; |
|
|
|
|
var successCb = function(data) { self._batchCb(data, handlers, self.allDoneCb); }; |
|
|
|
|
|
|
|
|
|
// No WebSocket, and no HTTP backend? This won't work.
|
|
|
|
|
if (self.jsonrpcclient.options.ajaxUrl === null) { |
|
|
|
|
throw "$.JsonRpcClient.batch used with no websocket and no http endpoint."; |
|
|
|
|
throw 'JsonRpcClient.batch used with no websocket and no http endpoint.'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Send request
|
|
|
|
|
$.ajax({ |
|
|
|
|
deferred = $.ajax({ |
|
|
|
|
url : self.jsonrpcclient.options.ajaxUrl, |
|
|
|
|
data : JSON.stringify(batch_request), |
|
|
|
|
contentType: 'application/json', |
|
|
|
|
data : this.jsonrpcclient.JSON.stringify(batchRequest), |
|
|
|
|
dataType : 'json', |
|
|
|
|
contentType: "application/json; charset=utf-8", |
|
|
|
|
cache : false, |
|
|
|
|
type : 'POST', |
|
|
|
|
headers : self.jsonrpcclient.options.headers, |
|
|
|
|
xhrFields : self.jsonrpcclient.options.xhrFields, |
|
|
|
|
|
|
|
|
|
// Batch-requests should always return 200
|
|
|
|
|
error : function(jqXHR, textStatus, errorThrown) { |
|
|
|
|
self.error_cb(jqXHR, textStatus, errorThrown); |
|
|
|
|
self.errorCb(jqXHR, textStatus, errorThrown); |
|
|
|
|
}, |
|
|
|
|
success : success_cb |
|
|
|
|
success : successCb |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return deferred; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Internal helper to match the result array from a batch call to their respective callbacks. |
|
|
|
|
* |
|
|
|
|
* @fn _batchCb |
|
|
|
|
* @memberof $.JsonRpcClient |
|
|
|
|
* @memberof JsonRpcClient |
|
|
|
|
*/ |
|
|
|
|
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) { |
|
|
|
|
JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, allDoneCb) { |
|
|
|
|
for (var i = 0; i < result.length; i++) { |
|
|
|
|
var response = result[i]; |
|
|
|
|
|
|
|
|
|
// Handle error
|
|
|
|
|
if ('error' in response) { |
|
|
|
|
if ('error' in response && response.error !== null) { |
|
|
|
|
if (response.id === null || !(response.id in handlers)) { |
|
|
|
|
// An error on a notify? Just log it to the console.
|
|
|
|
|
if ('console' in window) console.log(response); |
|
|
|
|
} |
|
|
|
|
else handlers[response.id].error_cb(response.error); |
|
|
|
|
if ('console' in window) { console.log(response); } |
|
|
|
|
} else { |
|
|
|
|
handlers[response.id].errorCb(response.error); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
} else { |
|
|
|
|
// Here we should always have a correct id and no error.
|
|
|
|
|
if (!(response.id in handlers) && 'console' in window) console.log(response); |
|
|
|
|
else handlers[response.id].success_cb(response.result); |
|
|
|
|
if (!(response.id in handlers) && 'console' in window) { |
|
|
|
|
console.log(response); |
|
|
|
|
} else { |
|
|
|
|
handlers[response.id].successCb(response.result); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (typeof all_done_cb === 'function') all_done_cb(result); |
|
|
|
|
if (typeof allDoneCb === 'function') { allDoneCb(result); } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
})(jQuery); |
|
|
|
|
$.JsonRpcClient = JsonRpcClient; |
|
|
|
|
|
|
|
|
|
})(this.jQuery); |
|
|
|
|