github.com/pojntfx/hydrapp/hydrapp@v0.0.0-20240516002902-d08759d6ca9f/pkg/generators/backend_react_panrpc.go.tpl (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/pojntfx/hydrapp/hydrapp/pkg/utils"
    16  	"github.com/pojntfx/panrpc/go/pkg/rpc"
    17  	"nhooyr.io/websocket"
    18  )
    19  
    20  type exampleStruct struct {
    21  	Name string `json:"name"`
    22  }
    23  
    24  type local struct {
    25  	ForRemotes func(
    26  		cb func(remoteID string, remote remote) error,
    27  	) error
    28  }
    29  
    30  func (l *local) ExamplePrintString(ctx context.Context, msg string) error {
    31  	fmt.Println(msg)
    32  
    33  	return nil
    34  }
    35  
    36  func (l *local) ExamplePrintStruct(ctx context.Context, input exampleStruct) error {
    37  	fmt.Println(input)
    38  
    39  	return nil
    40  }
    41  
    42  func (l *local) ExampleReturnError(ctx context.Context) error {
    43  	return errors.New("test error")
    44  }
    45  
    46  func (l *local) ExampleReturnString(ctx context.Context) (string, error) {
    47  	return "Test string", nil
    48  }
    49  
    50  func (l *local) ExampleReturnStruct(ctx context.Context) (exampleStruct, error) {
    51  	return exampleStruct{
    52  		Name: "Alice",
    53  	}, nil
    54  }
    55  
    56  func (l *local) ExampleReturnStringAndError(ctx context.Context) (string, error) {
    57  	return "Test string", errors.New("test error")
    58  }
    59  
    60  func (l *local) ExampleCallback(ctx context.Context) error {
    61  	var peer *remote
    62  
    63  	_ = l.ForRemotes(func(remoteID string, remote remote) error {
    64  		peer = &remote
    65  
    66  		return nil
    67  	})
    68  
    69  	if peer != nil {
    70  		ticker := time.NewTicker(time.Second)
    71  		i := 0
    72  		for {
    73  			if i >= 3 {
    74  				ticker.Stop()
    75  
    76  				return nil
    77  			}
    78  
    79  			<-ticker.C
    80  
    81  			if err := peer.ExampleNotification(ctx, "Backend time: "+time.Now().Format(time.RFC3339)); err != nil {
    82  				return err
    83  			}
    84  
    85  			i++
    86  		}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  func (s *local) ExampleClosure(
    93  	ctx context.Context,
    94  	length int,
    95  	onIteration func(ctx context.Context, i int, b string) (string, error),
    96  ) (int, error) {
    97  	for i := 0; i < length; i++ {
    98  		rv, err := onIteration(ctx, i, "This is from the backend")
    99  		if err != nil {
   100  			return -1, err
   101  		}
   102  
   103  		log.Println("Closure returned:", rv)
   104  	}
   105  
   106  	return length, nil
   107  }
   108  
   109  type remote struct {
   110  	ExampleNotification func(ctx context.Context, msg string) error
   111  }
   112  
   113  func StartServer(ctx context.Context, addr string, heartbeat time.Duration, localhostize bool) (string, func() error, error) {
   114  	if strings.TrimSpace(addr) == "" {
   115  		addr = ":0"
   116  	}
   117  
   118  	service := &local{}
   119  
   120  	clients := 0
   121  	registry := rpc.NewRegistry[remote, json.RawMessage](
   122  		service,
   123  
   124  		ctx,
   125  
   126  		&rpc.Options{
   127  			OnClientConnect: func(remoteID string) {
   128  				clients++
   129  
   130  				log.Printf("%v clients connected", clients)
   131  			},
   132  			OnClientDisconnect: func(remoteID string) {
   133  				clients--
   134  
   135  				log.Printf("%v clients connected", clients)
   136  			},
   137  		},
   138  	)
   139  	service.ForRemotes = registry.ForRemotes
   140  
   141  	listener, err := net.Listen("tcp", addr)
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  
   146  	log.Println("Listening on", listener.Addr())
   147  
   148  	go func() {
   149  		defer listener.Close()
   150  
   151  		if err := http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   152  			defer func() {
   153  				if err := recover(); err != nil {
   154  					w.WriteHeader(http.StatusInternalServerError)
   155  
   156  					log.Printf("Client disconnected with error: %v", err)
   157  				}
   158  			}()
   159  
   160  			switch r.Method {
   161  			case http.MethodGet:
   162  				c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
   163  					OriginPatterns: []string{"*"},
   164  				})
   165  				if err != nil {
   166  					panic(err)
   167  				}
   168  
   169  				pings := time.NewTicker(time.Second / 2)
   170  				defer pings.Stop()
   171  
   172  				errs := make(chan error)
   173  				go func() {
   174  					for range pings.C {
   175  						if err := c.Ping(ctx); err != nil {
   176  							errs <- err
   177  
   178  							return
   179  						}
   180  					}
   181  				}()
   182  
   183  				conn := websocket.NetConn(ctx, c, websocket.MessageText)
   184  				defer conn.Close()
   185  
   186  				encoder := json.NewEncoder(conn)
   187  				decoder := json.NewDecoder(conn)
   188  
   189  				go func() {
   190  					if err := registry.LinkStream(
   191  						func(v rpc.Message[json.RawMessage]) error {
   192  							return encoder.Encode(v)
   193  						},
   194  						func(v *rpc.Message[json.RawMessage]) error {
   195  							return decoder.Decode(v)
   196  						},
   197  
   198  						func(v any) (json.RawMessage, error) {
   199  							b, err := json.Marshal(v)
   200  							if err != nil {
   201  								return nil, err
   202  							}
   203  
   204  							return json.RawMessage(b), nil
   205  						},
   206  						func(data json.RawMessage, v any) error {
   207  							return json.Unmarshal([]byte(data), v)
   208  						},
   209  					); err != nil {
   210  						errs <- err
   211  
   212  						return
   213  					}
   214  				}()
   215  
   216  				if err := <-errs; err != nil {
   217  					panic(err)
   218  				}
   219  			default:
   220  				w.WriteHeader(http.StatusMethodNotAllowed)
   221  			}
   222  		})); err != nil {
   223  			if strings.HasSuffix(err.Error(), "use of closed network connection") {
   224  				return
   225  			}
   226  
   227  			panic(err)
   228  		}
   229  	}()
   230  
   231  	url, err := url.Parse("ws://" + listener.Addr().String())
   232  	if err != nil {
   233  		return "", nil, err
   234  	}
   235  
   236  	if localhostize {
   237  		return utils.Localhostize(url.String()), listener.Close, nil
   238  	}
   239  
   240  	return url.String(), listener.Close, nil
   241  }
   242