client.go

 1package client
 2
 3import (
 4	"context"
 5	"crypto/sha256"
 6	"encoding/hex"
 7	"encoding/json"
 8	"net"
 9	"net/http"
10	"path/filepath"
11	"time"
12
13	"github.com/charmbracelet/crush/internal/config"
14	"github.com/charmbracelet/crush/internal/server"
15)
16
17// Client represents an RPC client connected to a Crush server.
18type Client struct {
19	h    *http.Client
20	id   string
21	path string
22}
23
24// DefaultClient creates a new [Client] connected to the default server address.
25func DefaultClient(path string) (*Client, error) {
26	return NewClient(path, "unix", server.DefaultAddr())
27}
28
29// NewClient creates a new [Client] connected to the server at the given
30// network and address.
31func NewClient(path, network, address string) (*Client, error) {
32	var p http.Protocols
33	p.SetHTTP1(true)
34	p.SetUnencryptedHTTP2(true)
35	tr := http.DefaultTransport.(*http.Transport).Clone()
36	tr.Protocols = &p
37	tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
38		d := net.Dialer{
39			Timeout:   30 * time.Second,
40			KeepAlive: 30 * time.Second,
41		}
42		return d.DialContext(ctx, network, address)
43	}
44	h := &http.Client{
45		Transport: tr,
46	}
47	hasher := sha256.New()
48	hasher.Write([]byte(path))
49	id := hex.EncodeToString(hasher.Sum(nil))
50	return &Client{
51		h:    h,
52		id:   id,
53		path: filepath.Clean(path),
54	}, nil
55}
56
57// ID returns the client's instance unique identifier.
58func (c *Client) ID() string {
59	return c.id
60}
61
62// Path returns the client's instance filesystem path.
63func (c *Client) Path() string {
64	return c.path
65}
66
67// GetConfig retrieves the server's configuration via RPC.
68func (c *Client) GetConfig() (*config.Config, error) {
69	var cfg config.Config
70	rsp, err := c.h.Get("http://localhost/v1/config")
71	if err != nil {
72		return nil, err
73	}
74	defer rsp.Body.Close()
75	if err := json.NewDecoder(rsp.Body).Decode(&cfg); err != nil {
76		return nil, err
77	}
78	return &cfg, nil
79}