moonraker/test/client/js/json-rpc.js

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);
}
}
}