179 lines
3.8 KiB
Go
179 lines
3.8 KiB
Go
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
|
|
}
|