228 lines
7.8 KiB
JavaScript
228 lines
7.8 KiB
JavaScript
|
// Base JSON-RPC Client implementation
|
||
|
export default class JsonRPC {
|
||
|
constructor() {
|
||
|
this.id_counter = 0;
|
||
|
this.methods = new Object();
|
||
|
this.pending_callbacks = new Object();
|
||
|
this.transport = null;
|
||
|
}
|
||
|
|
||
|
_create_uid() {
|
||
|
let uid = this.id_counter;
|
||
|
this.id_counter++;
|
||
|
return uid.toString();
|
||
|
}
|
||
|
|
||
|
_build_request(method_name, uid, kwargs, ...args) {
|
||
|
let request = {
|
||
|
jsonrpc: "2.0",
|
||
|
method: method_name};
|
||
|
if (uid != null) {
|
||
|
request.id = uid;
|
||
|
}
|
||
|
if (kwargs != null) {
|
||
|
request.params = kwargs
|
||
|
}
|
||
|
else if (args.length > 0) {
|
||
|
request.params = args;
|
||
|
}
|
||
|
return request;
|
||
|
}
|
||
|
|
||
|
register_method(method_name, method) {
|
||
|
this.methods[method_name] = method
|
||
|
}
|
||
|
|
||
|
register_transport(transport) {
|
||
|
// The transport must have a send method. It should
|
||
|
// have an onmessage callback that fires when it
|
||
|
// receives data, but it would also be valid to directly call
|
||
|
// JsonRPC.process_received if necessary
|
||
|
this.transport = transport;
|
||
|
this.transport.onmessage = this.process_received.bind(this)
|
||
|
}
|
||
|
|
||
|
send_batch_request(requests) {
|
||
|
// Batch requests take an array of requests. Each request
|
||
|
// should be an object with the following attribtues:
|
||
|
// 'method' - The name of the method to execture
|
||
|
// 'type' - May be "request" or "notification"
|
||
|
// 'params' - method parameters, if applicable
|
||
|
//
|
||
|
// If a method has no parameters then the 'params' attribute
|
||
|
// should not be included.
|
||
|
|
||
|
if (this.transport == null)
|
||
|
return Promise.reject(Error("No Transport Initialized"));
|
||
|
|
||
|
let batch_request = [];
|
||
|
let promises = [];
|
||
|
requests.forEach((request, idx) => {
|
||
|
let name = request.method;
|
||
|
let args = [];
|
||
|
let kwargs = null;
|
||
|
let uid = null;
|
||
|
if ('params' in request) {
|
||
|
if (request.params instanceof Object)
|
||
|
kwargs = request.params;
|
||
|
else
|
||
|
args = request.params;
|
||
|
}
|
||
|
if (request.type == "request") {
|
||
|
uid = this._create_uid();
|
||
|
promises.push(new Promise((resolve, reject) => {
|
||
|
this.pending_callbacks[uid] = (result, error) => {
|
||
|
let response = {method: name, index: idx};
|
||
|
if (error != null) {
|
||
|
response.error = error;
|
||
|
reject(response);
|
||
|
} else {
|
||
|
response.result = result;
|
||
|
resolve(response);
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
}
|
||
|
batch_request.push(this._build_request(
|
||
|
name, uid, kwargs, ...args));
|
||
|
});
|
||
|
|
||
|
this.transport.send(JSON.stringify(batch_request));
|
||
|
return Promise.all(promises);
|
||
|
}
|
||
|
|
||
|
call_method(method_name, ...args) {
|
||
|
let uid = this._create_uid();
|
||
|
let request = this._build_request(
|
||
|
method_name, uid, null, ...args);
|
||
|
if (this.transport != null) {
|
||
|
this.transport.send(JSON.stringify(request));
|
||
|
return new Promise((resolve, reject) => {
|
||
|
this.pending_callbacks[uid] = (result, error) => {
|
||
|
if (error != null) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
resolve(result);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return Promise.reject(Error("No Transport Initialized"));
|
||
|
}
|
||
|
|
||
|
call_method_with_kwargs(method_name, kwargs) {
|
||
|
let uid = this._create_uid();
|
||
|
let request = this._build_request(method_name, uid, kwargs);
|
||
|
if (this.transport != null) {
|
||
|
this.transport.send(JSON.stringify(request));
|
||
|
return new Promise((resolve, reject) => {
|
||
|
this.pending_callbacks[uid] = (result, error) => {
|
||
|
if (error != null) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
resolve(result);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return Promise.reject(Error("No Transport Initialized"));
|
||
|
}
|
||
|
|
||
|
notify(method_name, ...args) {
|
||
|
let notification = this._build_request(
|
||
|
method_name, null, null, ...args);
|
||
|
if (this.transport != null) {
|
||
|
this.transport.send(JSON.stringify(notification));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
process_received(encoded_data) {
|
||
|
let rpc_data = JSON.parse(encoded_data);
|
||
|
if (rpc_data instanceof Array) {
|
||
|
// batch request/response
|
||
|
for (let data of rpc_data) {
|
||
|
this._validate_and_dispatch(data);
|
||
|
}
|
||
|
} else {
|
||
|
this._validate_and_dispatch(rpc_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_validate_and_dispatch(rpc_data) {
|
||
|
if (rpc_data.jsonrpc != "2.0") {
|
||
|
console.log("Invalid JSON-RPC data");
|
||
|
console.log(rpc_data);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ("result" in rpc_data || "error" in rpc_data) {
|
||
|
// This is a response to a client request
|
||
|
this._handle_response(rpc_data);
|
||
|
} else if ("method" in rpc_data) {
|
||
|
// This is a server side notification/event
|
||
|
this._handle_request(rpc_data);
|
||
|
} else {
|
||
|
// Invalid RPC data
|
||
|
console.log("Invalid JSON-RPC data");
|
||
|
console.log(rpc_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_handle_request(request) {
|
||
|
// Note: This implementation does not fully conform
|
||
|
// to the JSON-RPC protocol. The server only sends
|
||
|
// events (notifications) to the client, and it is
|
||
|
// not concerned with client-side errors. Thus
|
||
|
// this implementation does not attempt to track
|
||
|
// request id's, nor does it send responses back
|
||
|
// to the server
|
||
|
let method = this.methods[request.method];
|
||
|
if (method == null) {
|
||
|
console.log("Invalid Method: " + request.method);
|
||
|
return;
|
||
|
}
|
||
|
if ("params" in request) {
|
||
|
let args = request.params;
|
||
|
if (args instanceof Array)
|
||
|
method(...args);
|
||
|
else if (args instanceof Object) {
|
||
|
// server passed keyword arguments which we currently do not support
|
||
|
console.log("Keyword Parameters Not Supported:");
|
||
|
console.log(request);
|
||
|
} else {
|
||
|
console.log("Invalid Parameters");
|
||
|
console.log(request);
|
||
|
}
|
||
|
} else {
|
||
|
method();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_handle_response(response) {
|
||
|
if (response.result != null && response.id != null) {
|
||
|
let uid = response.id;
|
||
|
let response_finalize = this.pending_callbacks[uid];
|
||
|
if (response_finalize != null) {
|
||
|
response_finalize(response.result);
|
||
|
delete this.pending_callbacks[uid];
|
||
|
} else {
|
||
|
console.log("No Registered RPC Call for uid:");
|
||
|
console.log(response);
|
||
|
}
|
||
|
} else if (response.error != null) {
|
||
|
// Check ID, depending on the error it may or may not be available
|
||
|
let uid = response.id;
|
||
|
let response_finalize = this.pending_callbacks[uid];
|
||
|
if (response_finalize != null) {
|
||
|
response_finalize(null, response.error);
|
||
|
delete this.pending_callbacks[uid];
|
||
|
} else {
|
||
|
console.log("JSON-RPC error recieved");
|
||
|
console.log(response.error);
|
||
|
}
|
||
|
} else {
|
||
|
console.log("Invalid JSON-RPC response");
|
||
|
console.log(response);
|
||
|
}
|
||
|
}
|
||
|
}
|