spot-sdk-js
Version:
Develop applications and payloads for Spot using the unofficial Boston Dynamics Spot Node.js SDK.
458 lines (391 loc) • 13.1 kB
JavaScript
const license_pb = require('../bosdyn/api/license_pb');
const header_pb = require('../bosdyn/api/header_pb');
const grpc = require('@grpc/grpc-js');
const {cloneDeep} = require('lodash');
const net = require('net');
const grpcPromise = require('grpc-promise');
const {translate_exception} = require('./channel');
const DEFAULT_RPC_TIMEOUT = 30000; // seconds
function popObject(obj, key, defaultVal) {
var ret = obj[key];
if(ret == undefined){
ret = defaultVal;
}else{
delete obj[key];
}
return ret;
}
function common_header_errors(response){
if(response?.getHeader()?.getError()?.getCode() == header_pb.CommonError.Code.CODE_UNSPECIFIED){
return UnsetStatusError(response);
}
if(response?.getHeader()?.getError()?.getCode() == header_pb.CommonError.Code.CODE_INTERNAL_SERVER_ERROR){
return InternalServerError(response);
}
if(response?.getHeader()?.getError()?.getCode() == header_pb.CommonError.Code.CODE_INVALID_REQUEST){
return InvalidRequestError(response);
}
return null;
}
function streaming_common_header_errors(response_iterator){
for(const response of response_iterator){
let error = common_header_errors(response)
if(error != null) return error;
}
return null;
}
function common_lease_errors(response){
let lease_use_results = [];
if(response.hasLeaseUseResult()){
lease_use_results = [response.getLeaseUseResult()];
}else if(response.hasOwnProperty('lease_use_results')){
lease_use_results = response.lease_use_results;
}else{
return InternalServerError(response, 'No LeaseUseResult field found!');
}
for(const result in lease_use_results){
if(result.getStatus() != result.Status.STATUS_OK){
return LeaseUseError(response);
}
}
return null;
}
function streaming_common_lease_errors(response_iterator){
for(const response in response_iterator){
const error = common_lease_errors(response);
if(error != null){
return error;
}
}
return null;
}
function error_pair(error_message){
return [error_message, error_message.errorString];
}
function error_factory(response, status, status_to_string, status_to_error){
const error_type = status_to_error[status][0];
let message = status_to_error[status][1];
if(error_type == null) return null;
if(message == null){
try{
status_str = status_to_string[status];
}catch(e){
if(e instanceof Error){
message = `Code: ${status} (Protobuf definition mismatch?)`;
}else{
message = `Code: ${status} (${status_str})`;
}
}
}
if(Array.isArray(response)){
for(const resp of response){
const err = new error_type({response: resp, error_message: message});
if(err != null) return err;
}
return null;
}else{
return new error_type({response: response, error_message: message});
}
}
function handle_unset_status_error(unset, field = 'status', statustype = null, args, func){
if(Array.isArray(args)){
for(const resp of args){
const _statustype = statustype ? statustype : resp;
if(resp[field] == _statustype[unset]){
return Error(resp);
}
}
}else{
const _statustype = statustype ? statustype : args[0];
if(args[0][field] == _statustype[unset]){
return Error(args[0]);
}
}
return func(args);
}
function handle_common_header_errors(args, func){
if(Array.isArray(args)){
return streaming_common_header_errors(args) || func(args);
}else{
return common_header_errors(args) || func(args);
}
}
function handle_lease_use_result_errors(args, func){
if(Array.isArray(args)){
return streaming_common_lease_errors(args) || func(args);
}else{
return common_lease_errors(args) || func(args);
}
}
function print_response(args, func){
function print_message(response){
console.log(response);
}
function print_streaming_message(response_iterator){
for(const response in response_iterator){
print_message(response)
}
}
if(Array.isArray(args)){
print_streaming_message(args);
}else{
print_message(args);
}
return func(args);
}
function process_args(func){
function processor(rpc_method, request, value_from_response = null, error_from_response = null, args = {}){
if(args.hasOwnProperty("disable_value_handler")){
value_from_response = null;
}
delete args["disable_value_handler"];
if(args.hasOwnProperty("disable_error_handler")){
error_from_response = null;
}
delete args["disable_error_handler"];
return func(rpc_method, request, value_from_response, error_from_response, args);
}
return processor;
}
class BaseClient {
_SPLIT_SERVICE = '.';
_SPLIT_METHOD = '/';
constructor(stub_creation_func, name = null){
this._service_type_short = this.constructor.service_type.split(BaseClient._SPLIT_SERVICE).slice(-1);
console.log(this._service_type_short)
this._channel = null;
this._logger = null;
this._name = name;
this._stub = null;
this._stub_creation_func = stub_creation_func;
this.logger = console;
this.request_processors = [];
this.response_processors = [];
this.lease_wallet = null;
}
static request_trim_for_log(req){
return `\n${JSON.stringify(req.toObject())}\n`;
}
static response_trim_for_log(resp){
return `\n${JSON.stringify(resp.toObject())}\n`;
}
get channel(){
if(this._channel == null) throw new Error('Client channel is unset!');
return this._channel;
}
set channel(channel){
const grpc = require('@grpc/grpc-js');
console.log('Pensez à modif common.js pour retirer insecure credentials')
this._channel = channel;
this._stub = new this._stub_creation_func(channel.defaultAuthority, /*channel.credentials.channelCredentials*/ grpc.credentials.createInsecure());
}
update_from(other){
this.request_processors = this.request_processors.concat(other.request_processors);
this.response_processors = this.response_processors.concat(other.response_processors);
this.logger = other.logger;
// this.logger = other.logger.getChild(this._name || this._service_type_short);
this.lease_wallet = other.lease_wallet;
this.client_name = other.client_name;
}
_apply_request_processors(request){
if(request == null) return;
for(const proc of this.request_processors){
proc.mutate(request)
}
return request;
}
_apply_response_processors(response){
if(response == null) return;
for(const proc of this.response_processors){
console.log(proc)
proc.mutate(response)
}
return response;
}
_get_logger(rpc_method){
return this.logger;
}
update_request_iterator(request_iterator, logger, rpc_method, is_blocking){
let a = [];
for(let request of request_iterator){
request = this._apply_request_processors(cloneDeep(request));
if(is_blocking){
logger.debug(`[COMMON] blocking request: ${rpc_method._method} ${BaseClient.request_trim_for_log(request)}`);
}else{
logger.debug(`[COMMON] async request: ${rpc_method._method} ${BaseClient.request_trim_for_log(request)}`);
}
a.push(request);
}
console.log(a);
return a;
}
update_response_iterator(response_iterator, logger, rpc_method, is_blocking){
try{
let a = [];
for(let response in response_iterator){
response = this._apply_response_processors(cloneDeep(response));
if(is_blocking){
logger.debug(`[COMMON] blocking response: ${rpc_method._method} ${BaseClient.request_trim_for_log(response)}`);
}else{
logger.debug(`[COMMON] async response: ${rpc_method._method} ${BaseClient.request_trim_for_log(response)}`);
}
a.push(response);
}
return a;
}catch(e){
throw translate_exception(e);
}
}
#make(rpc_method, request, args){
const timeout = popObject(args, 'timeout', DEFAULT_RPC_TIMEOUT);
rpc_method = rpc_method.bind(this._stub);
return new Promise((resolve, reject) => {
rpc_method(request, {deadline: Date.now() + timeout}, function(err, response){
if(err) return reject(err);
return resolve(response);
});
});
}
async call(rpc_method, request, value_from_response = null, error_from_response = null, args = {}){
const logger = this.logger;
const path = rpc_method.path;
if(rpc_method.requestStream){
request = this.update_request_iterator(request, logger, rpc_method, true);
}else{
request = this._apply_request_processors(cloneDeep(request))
logger.debug(`[COMMON] blocking request: ${path} ${BaseClient.request_trim_for_log(request)}`);
}
let response;
try{
response = await this.#make(rpc_method, request, args);
}catch(err){
console.log('Pensez à retirer le log du catch');
console.log(err);
throw translate_exception(err);
}
if(rpc_method.responseStream){
const res = this.update_response_iterator(response, logger, rpc_method, true);
return this.handle_response_streaming([...res], error_from_response, value_from_response);
}else{
const res = this._apply_response_processors(response);
logger.debug(`[COMMON] response: ${path} ${BaseClient.response_trim_for_log(res)}`);
return this.handle_response(res, error_from_response, value_from_response);
}
}
handle_response(response, error_from_response, value_from_response){
const exc = error_from_response != null ? error_from_response(response) : null;
if(exc != null){
throw exc;
}
if(value_from_response == null){
return response;
}
return value_from_response(response);
}
handle_response_streaming(response, error_from_response, value_from_response){
if(error_from_response != null){
var exc = error_from_response(response.map());
}else{
var exc = null;
}
if(exc != null){
throw exc;
}
if(value_from_response == null){
return response;
}
return value_from_response(response.map());
}
call_async(rpc_method, request, value_from_response = null, error_from_response = null, args){
request = this._apply_request_processors(cloneDeep(request));
const logger = this.logger;
logger.debug(`[COMMON] async request: ${rpc_method._method} ${BaseClient.request_trim_for_log(request)}`);
const response_future = rpc_method.future(request, args);
function on_finish(fut){
let isCatch = false;
try{
var result = fut.result();
}catch(e){
isCatch = true;
logger.debug(`[COMMON] async exception: ${rpc_method._method}\n${e}\n`);
}
if(!isCatch){
let isCatch2 = false;
try{
this._apply_response_processors(result);
}catch(e){
isCatch2 = true;
logger.warn("[COMMON] Error applying response processors.");
}
if(!isCatch2){
logger.debug(`[COMMON] async response: ${rpc_method._method} ${BaseClient.response_trim_for_log(result)}`);
}
}
}
response_future.add_done_callback(on_finish);
return new FutureWrapper(response_future, value_from_response, error_from_response);
}
};
class FutureWrapper {
constructor(future, value_from_response, error_from_response){
this.original_future = future;
this._error_from_response = error_from_response;
this._value_from_response = value_from_response;
}
toString(){
return this.original_future.toString();
}
cancel(){
return this.original_future.cancel();
}
cancelled(){
return this.original_future.cancelled();
}
running(){
return this.original_future.running();
}
done(){
return this.original_future.done();
}
traceback(args){
return this.original_future.traceback(args);
}
add_done_callback(cb){
this.original_future.add_done_callback(cb(this));
}
result(args){
error = this.exception();
if(error != null) throw error;
const base_result = this.original_future.result(args);
if(this._value_from_response == null) return base_result;
return this._value_from_response(base_result);
}
exception(args){
error = this.original_future.exception(args);
if(error == null){
if(this._error_from_response == null) return null;
return this._error_from_response(this.original_future.result())
}
return translate_exception(error);
}
};
function get_self_ip(){
const {address} = require('ip');
return address();
}
module.exports = {
BaseClient,
FutureWrapper,
common_header_errors,
streaming_common_header_errors,
common_lease_errors,
streaming_common_lease_errors,
error_pair,
error_factory,
handle_unset_status_error,
handle_common_header_errors,
handle_lease_use_result_errors,
print_response,
process_args,
get_self_ip
};