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 Timeout: 0, // we need this to be 0 for long-lived connections and SSE streams
47 }
48 hasher := sha256.New()
49 hasher.Write([]byte(path))
50 id := hex.EncodeToString(hasher.Sum(nil))
51 return &Client{
52 h: h,
53 id: id,
54 path: filepath.Clean(path),
55 }, nil
56}
57
58// ID returns the client's instance unique identifier.
59func (c *Client) ID() string {
60 return c.id
61}
62
63// Path returns the client's instance filesystem path.
64func (c *Client) Path() string {
65 return c.path
66}
67
68// GetGlobalConfig retrieves the server's configuration via RPC.
69func (c *Client) GetGlobalConfig() (*config.Config, error) {
70 var cfg config.Config
71 rsp, err := c.h.Get("http://localhost/v1/config")
72 if err != nil {
73 return nil, err
74 }
75 defer rsp.Body.Close()
76 if err := json.NewDecoder(rsp.Body).Decode(&cfg); err != nil {
77 return nil, err
78 }
79 return &cfg, nil
80}