diff --git a/clientoptions.go b/clientoptions.go index 4aed5e5..09939d7 100644 --- a/clientoptions.go +++ b/clientoptions.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/cenkalti/backoff/v4" + "reflect" + "strings" ) // WithConnection sets the Connection of the Client @@ -94,9 +96,20 @@ func WithHttpConnection(ctx context.Context, address string, options ...func(*ht func WithReceiver(receiver interface{}) func(Party) error { return func(party Party) error { if client, ok := party.(*client); ok { + if client.receiver != nil { + return errors.New("client already has a receiver") + } client.receiver = receiver if receiver, ok := receiver.(ReceiverInterface); ok { receiver.Init(client) + + hubType := reflect.TypeOf(receiver) + hubValue := reflect.ValueOf(receiver) + + for i := 0; i < hubType.NumMethod(); i++ { + methodName := strings.ToLower(hubType.Method(i).Name) + receiver.SetFunc(methodName, hubValue.Method(i)) + } } return nil } @@ -104,6 +117,25 @@ func WithReceiver(receiver interface{}) func(Party) error { } } +// ReceiveFunc set the handler function, and it will be added to the receiver. a default one will be used, If there is no receiver. +func ReceiveFunc(name string, fn interface{}) func(Party) error { + return func(party Party) error { + if client, ok := party.(*client); ok { + if client.receiver == nil { + receiver := &Receiver{} + receiver.Init(client) + client.receiver = receiver + } + if receiver, ok := client.receiver.(ReceiverInterface); ok { + receiver.SetFunc(name, fn) + return nil + } + return errors.New("existing receiver does not support adding methods") + } + return errors.New("option ReceiveFunc is client only") + } +} + // WithBackoff sets the backoff.BackOff used for repeated connection attempts in the client. // See https://pkg.go.dev/github.com/cenkalti/backoff for configuration options. // If the option is not set, backoff.NewExponentialBackOff() without any further configuration will be used. diff --git a/loop.go b/loop.go index 02b16b5..5a820ea 100644 --- a/loop.go +++ b/loop.go @@ -369,13 +369,20 @@ func buildMethodArguments(method reflect.Value, invocation invocationMessage, } func getMethod(target interface{}, name string) (reflect.Value, bool) { + if receiver, ok := target.(ReceiverInterface); ok { + if method := receiver.GetFunc(name); method != nil { + return reflect.ValueOf(method), true + } + } + hubType := reflect.TypeOf(target) if hubType != nil { hubValue := reflect.ValueOf(target) - name = strings.ToLower(name) + lowCaseName := strings.ToLower(name) + for i := 0; i < hubType.NumMethod(); i++ { // Search in public methods - if m := hubType.Method(i); strings.ToLower(m.Name) == name { + if m := hubType.Method(i); strings.ToLower(m.Name) == lowCaseName { return hubValue.Method(i), true } } diff --git a/receiver.go b/receiver.go index 1396fb7..dc4c09c 100644 --- a/receiver.go +++ b/receiver.go @@ -1,20 +1,35 @@ package signalr // ReceiverInterface allows receivers to interact with the server directly from the receiver methods -// Init(Client) +// +// Init(Client) +// // Init is used by the Client to connect the receiver to the server. -// Server() Client +// +// Server() Client +// // Server can be used inside receiver methods to call Client methods, // e.g. Client.Send, Client.Invoke, Client.PullStream and Client.PushStreams +// +// GetFunc(string) interface{} +// +// GetFunc is used to get the handler function by handler name. +// +// SetFunc(string, interface{}) +// +// SetFunc is used to set the mapping between the name and the handler function. type ReceiverInterface interface { Init(Client) Server() Client + GetFunc(string) interface{} + SetFunc(string, interface{}) } // Receiver is a base class for receivers in the client. // It implements ReceiverInterface type Receiver struct { - client Client + methodMap map[string]interface{} + client Client } // Init is used by the Client to connect the receiver to the server. @@ -26,3 +41,25 @@ func (ch *Receiver) Init(client Client) { func (ch *Receiver) Server() Client { return ch.client } + +// GetFunc is used to get the handler function by handler name. +func (ch *Receiver) GetFunc(name string) interface{} { + return ch.methodMap[name] +} + +// SetFunc is used to set the mapping between the name and the handler function. +func (ch *Receiver) SetFunc(name string, fn interface{}) { + if ch.methodMap == nil { + ch.methodMap = make(map[string]interface{}) + } + if _, ok := ch.methodMap[name]; ok { + info, _ := ch.client.loggers() + _ = info.Log(evt, "set function", "warn", "method has been overridden", "method", name) + } + ch.methodMap[name] = fn +} + +// On is an alias for SetFunc +func (ch *Receiver) On(name string, fn interface{}) { + ch.SetFunc(name, fn) +}