package js import ( "errors" "github.com/dop251/goja" "src.dualinventive.com/go/dinet/rpc" ) var errNotFound = errors.New("not found") // requestHandler registered by javascript type requestHandler struct { goja.Callable classMethod rpc.ClassMethod uid uint16 } type requestHandlers []requestHandler func (s *Simulator) getRequestHandler(classMethod rpc.ClassMethod, uid uint16) (goja.Callable, error) { // Search for for _, h := range s.requestHandlers { if h.classMethod == classMethod && h.uid == uid { return h.Callable, nil } } if uid == 0 { return nil, errNotFound } // Try to get the catch-all request handler for the class:method instead of UID specific handler for _, h := range s.requestHandlers { if h.classMethod == classMethod { return h.Callable, nil } } return nil, errNotFound } func (s *Simulator) replyWithError(req *rpc.Msg, code rpc.ErrorCode) { err := s.m.Send(req.CreateReply().SetError(code)) if err != nil { s.m.Error("unable to send reply") } } func (s *Simulator) msgToJSObject(msg *rpc.Msg) (*goja.Object, error) { this := s.vm.NewObject() err := this.Set("Msg", s.vm.ToValue(msg)) if err != nil { return nil, err } var params map[string]interface{} err = msg.Params.Unmarshal(¶ms) if err != nil && err != rpc.ErrMsgNoData { return nil, err } err = this.Set("Params", s.vm.ToValue(params)) if err != nil { return nil, err } err = this.Set("Result", s.vm.NewObject()) if err != nil { return nil, err } return this, nil } func getJSResult(this *goja.Object) interface{} { switch result := this.Get("Result").Export().(type) { case map[string]interface{}: if len(result) == 0 { break } return result } return nil } func jsToReplyMsg(replyMsg *rpc.Msg, this goja.Value) { switch rep := this.Export().(type) { case *rpc.Msg: case map[string]interface{}: if err, ok := rep["error"]; ok { code, ok := err.(int64) if ok { replyMsg.Error = rpc.GetError(rpc.ErrorCode(code)) } } if result, ok := rep["result"]; ok { replyMsg.Result = rpc.NewResult(result) } } } func (s *Simulator) requestHandleMsg(msg *rpc.Msg) { var param rpc.ConfigParam if msg.Params != nil { err := msg.Params.Unmarshal(¶m) if err != nil && err != rpc.ErrMsgNoData { s.m.Error("RPC request params invalid") } } requestHandler, err := s.getRequestHandler(msg.ClassMethod, param.UID) if err != nil { s.replyWithError(msg, rpc.EIofailed) return } fn := func() { this, err := s.msgToJSObject(msg) if err != nil { s.replyWithError(msg, rpc.EIofailed) return } rhv, err := requestHandler(this) if err != nil { s.replyWithError(msg, rpc.EIofailed) return } replyMsg := msg.CreateReply() result := getJSResult(this) if result != nil { replyMsg.Result = rpc.NewResult([]interface{}{result}) } else { jsToReplyMsg(replyMsg, rhv) } err = s.m.Send(replyMsg) if err != nil { s.m.Error("unable to send reply") } } s.dispatchC <- fn } // Register a request handler from javascript func (s *Simulator) requestRegister(jsFunction, classMethod, uid goja.Value) { hUID := uint16(uid.ToInteger()) hClassMethod := rpc.ClassMethod(classMethod.String()) hJsFunction, ok := goja.AssertFunction(jsFunction) if !ok { // TODO return } rHandler := requestHandler{Callable: hJsFunction, uid: hUID, classMethod: hClassMethod} s.requestHandlers = append(s.requestHandlers, rHandler) } // request registers a javascript DI-Net RPC message request handler func (s *Simulator) request(call goja.FunctionCall) goja.Value { switch len(call.Arguments) { case 2: // string("class:method"), function s.requestRegister(call.Argument(1), call.Argument(0), goja.Undefined()) case 3: // string("class:method"), integer(uid), function s.requestRegister(call.Argument(2), call.Argument(0), call.Argument(1)) } // TODO error return nil }