mirror of
https://github.com/twisterarmy/twister-calm.git
synced 2025-01-13 08:28:01 +00:00
b54e611797
change files order, new DHT indicator in top menu, ‘back’ button in DM conversations, sound notifications (alpha) and new page ‘Options’, few fixes
441 lines
15 KiB
JavaScript
441 lines
15 KiB
JavaScript
/**
|
|
* This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
|
|
* $.parseJSON.
|
|
*
|
|
* The plan is to make use of websockets if they are available, but work just as well with only
|
|
* http if not.
|
|
*
|
|
* Usage example:
|
|
*
|
|
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
|
|
* foo.call(
|
|
* 'bar', [ 'A parameter', 'B parameter' ],
|
|
* function(result) { alert('Foo bar answered: ' + result.my_answer); },
|
|
* function(error) { console.log('There was an error', error); }
|
|
* );
|
|
*
|
|
* More examples are available in README.md
|
|
*/
|
|
(function($) {
|
|
/**
|
|
* @fn new
|
|
* @memberof $.JsonRpcClient
|
|
*
|
|
* @param options An object stating the backends:
|
|
* ajaxUrl A url (relative or absolute) to a http(s) backend.
|
|
* socketUrl A url (relative of absolute) to a ws(s) backend.
|
|
* onmessage A socket message handler for other messages (non-responses).
|
|
* 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.
|
|
*/
|
|
$.JsonRpcClient = function(options) {
|
|
var self = this;
|
|
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); }
|
|
}, 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;
|
|
|
|
/// Object <id>: { success_cb: cb, error_cb: cb }
|
|
$.JsonRpcClient.prototype._ws_callbacks = {};
|
|
|
|
/// The next JSON-RPC request id.
|
|
$.JsonRpcClient.prototype._current_id = 1;
|
|
|
|
/**
|
|
* @fn call
|
|
* @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.
|
|
*/
|
|
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
|
|
// 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
|
|
};
|
|
|
|
// Try making a WebSocket call.
|
|
var socket = this.options.getSocket(this.wsOnMessage);
|
|
if (socket !== null) {
|
|
this._wsCall(socket, request, success_cb, error_cb);
|
|
return;
|
|
}
|
|
|
|
// 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.";
|
|
}
|
|
var options = this.options;
|
|
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : this.options.ajaxUrl,
|
|
data : $.toJSON(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));
|
|
}
|
|
},
|
|
|
|
success : function(data) {
|
|
if ('error' in data) error_cb(data.error);
|
|
success_cb(data.result);
|
|
},
|
|
|
|
// JSON-RPC Server could return non-200 on error
|
|
error : function(jqXHR, textStatus, errorThrown) {
|
|
try {
|
|
var response = $.parseJSON(jqXHR.responseText);
|
|
if ('console' in window) console.log(response);
|
|
error_cb(response.error);
|
|
}
|
|
catch (err) {
|
|
// Perhaps the responseText wasn't really a jsonrpc-error.
|
|
error_cb({ error: jqXHR.responseText });
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Notify sends a command to the server that won't need a response. In http, there is probably
|
|
* an empty response - that will be dropped, but in ws there should be no response at all.
|
|
*
|
|
* This is very similar to call, but has no id and no handling of callbacks.
|
|
*
|
|
* @fn notify
|
|
* @memberof $.JsonRpcClient
|
|
*
|
|
* @param method The method to run on JSON-RPC server.
|
|
* @param params The params; an array or object.
|
|
*/
|
|
$.JsonRpcClient.prototype.notify = function(method, params) {
|
|
// Construct the JSON-RPC 2.0 request.
|
|
var request = {
|
|
jsonrpc: '2.0',
|
|
method: method,
|
|
params: params
|
|
};
|
|
|
|
// Try making a WebSocket call.
|
|
var socket = this.options.getSocket(this.wsOnMessage);
|
|
if (socket !== null) {
|
|
this._wsCall(socket, request);
|
|
return;
|
|
}
|
|
|
|
// 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.";
|
|
}
|
|
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : this.options.ajaxUrl,
|
|
data : $.toJSON(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));
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
* sent as a batch call then the callback is done.
|
|
*
|
|
* @fn batch
|
|
* @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.
|
|
* 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);
|
|
callback(batch);
|
|
batch._execute();
|
|
};
|
|
|
|
/**
|
|
* The default getSocket handler.
|
|
*
|
|
* @param onmessage_cb The callback to be bound to onmessage events on the socket.
|
|
*
|
|
* @fn _getSocket
|
|
* @memberof $.JsonRpcClient
|
|
*/
|
|
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
|
|
// 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._ws_socket === null || this._ws_socket.readyState > 1) {
|
|
// No socket, or dying socket, let's get a new one.
|
|
this._ws_socket = new WebSocket(this.options.socketUrl);
|
|
|
|
// Set up onmessage handler.
|
|
this._ws_socket.onmessage = onmessage_cb;
|
|
}
|
|
|
|
return this._ws_socket;
|
|
};
|
|
|
|
/**
|
|
* Internal handler to dispatch a JRON-RPC request through a websocket.
|
|
*
|
|
* @fn _wsCall
|
|
* @memberof $.JsonRpcClient
|
|
*/
|
|
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
|
|
var request_json = $.toJSON(request);
|
|
|
|
if (socket.readyState < 1) {
|
|
// 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.
|
|
|
|
// Set up sending of message for when the socket is open.
|
|
socket.onopen = function() {
|
|
// Send the request.
|
|
socket.send(request_json);
|
|
};
|
|
}
|
|
else {
|
|
// We have a socket and it should be ready to send on.
|
|
socket.send(request_json);
|
|
}
|
|
|
|
// 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 };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Internal handler for the websocket messages. It determines if the message is a JSON-RPC
|
|
* 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.
|
|
*/
|
|
$.JsonRpcClient.prototype._wsOnMessage = function(event) {
|
|
// Check if this could be a JSON RPC message.
|
|
try {
|
|
var response = $.parseJSON(event.data);
|
|
|
|
/// @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') {
|
|
|
|
/// @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]) {
|
|
// Get the success callback.
|
|
var success_cb = this._ws_callbacks[response.id].success_cb;
|
|
|
|
// Delete the callback from the storage.
|
|
delete this._ws_callbacks[response.id];
|
|
|
|
// Run callback with result as parameter.
|
|
success_cb(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]) {
|
|
// Get the error callback.
|
|
var error_cb = this._ws_callbacks[response.id].error_cb;
|
|
|
|
// Delete the callback from the storage.
|
|
delete this._ws_callbacks[response.id];
|
|
|
|
// Run callback with the error object as parameter.
|
|
error_cb(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') {
|
|
this.options.onmessage(event);
|
|
}
|
|
};
|
|
|
|
|
|
/************************************************************************************************
|
|
* Batch object with methods
|
|
************************************************************************************************/
|
|
|
|
/**
|
|
* Handling object for batch calls.
|
|
*/
|
|
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
|
|
// 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.
|
|
this._requests = [];
|
|
|
|
this.jsonrpcclient = jsonrpcclient;
|
|
this.all_done_cb = all_done_cb;
|
|
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {};
|
|
|
|
};
|
|
|
|
/**
|
|
* @sa $.JsonRpcClient.prototype.call
|
|
*/
|
|
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
|
|
this._requests.push({
|
|
request : {
|
|
jsonrpc : '2.0',
|
|
method : method,
|
|
params : params,
|
|
id : this.jsonrpcclient._current_id++ // Use the client's id series.
|
|
},
|
|
success_cb : success_cb,
|
|
error_cb : error_cb
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @sa $.JsonRpcClient.prototype.notify
|
|
*/
|
|
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
|
|
this._requests.push({
|
|
request : {
|
|
jsonrpc : '2.0',
|
|
method : method,
|
|
params : params
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Executes the batched up calls.
|
|
*/
|
|
$.JsonRpcClient._batchObject.prototype._execute = function() {
|
|
var self = this;
|
|
|
|
if (this._requests.length === 0) return; // All done :P
|
|
|
|
// Collect all request data and sort handlers by request id.
|
|
var batch_request = [];
|
|
var handlers = {};
|
|
|
|
// 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) {
|
|
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 (typeof all_done_cb === 'function') all_done_cb(result);
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < this._requests.length; i++) {
|
|
var call = this._requests[i];
|
|
batch_request.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
|
|
};
|
|
}
|
|
}
|
|
|
|
var success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
|
|
|
|
// 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.";
|
|
}
|
|
|
|
// Send request
|
|
$.ajax({
|
|
url : self.jsonrpcclient.options.ajaxUrl,
|
|
data : $.toJSON(batch_request),
|
|
dataType : 'json',
|
|
contentType: "application/json; charset=utf-8",
|
|
cache : false,
|
|
type : 'POST',
|
|
|
|
// Batch-requests should always return 200
|
|
error : function(jqXHR, textStatus, errorThrown) {
|
|
self.error_cb(jqXHR, textStatus, errorThrown);
|
|
},
|
|
success : success_cb
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Internal helper to match the result array from a batch call to their respective callbacks.
|
|
*
|
|
* @fn _batchCb
|
|
* @memberof $.JsonRpcClient
|
|
*/
|
|
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
|
|
for (var i = 0; i < result.length; i++) {
|
|
var response = result[i];
|
|
|
|
// Handle error
|
|
if ('error' in response) {
|
|
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);
|
|
}
|
|
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 (typeof all_done_cb === 'function') all_done_cb(result);
|
|
};
|
|
|
|
})(jQuery);
|