1package client
2
3import (
4 "context"
5 "encoding/json"
6 "net"
7 "net/http"
8 "path/filepath"
9 "time"
10
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/server"
13)
14
15// Client represents an RPC client connected to a Crush server.
16type Client struct {
17 h *http.Client
18 id string
19 path string
20}
21
22// DefaultClient creates a new [Client] connected to the default server address.
23func DefaultClient(path string) (*Client, error) {
24 return NewClient(path, "unix", server.DefaultAddr())
25}
26
27// NewClient creates a new [Client] connected to the server at the given
28// network and address.
29func NewClient(path, network, address string) (*Client, error) {
30 var p http.Protocols
31 p.SetHTTP1(true)
32 p.SetUnencryptedHTTP2(true)
33 tr := http.DefaultTransport.(*http.Transport).Clone()
34 tr.Protocols = &p
35 tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
36 d := net.Dialer{
37 Timeout: 30 * time.Second,
38 KeepAlive: 30 * time.Second,
39 }
40 return d.DialContext(ctx, network, address)
41 }
42 h := &http.Client{
43 Transport: tr,
44 Timeout: 0, // we need this to be 0 for long-lived connections and SSE streams
45 }
46 return &Client{
47 h: h,
48 path: filepath.Clean(path),
49 }, nil
50}
51
52// ID returns the client's instance unique identifier.
53func (c *Client) ID() string {
54 return c.id
55}
56
57// SetID sets the client's instance unique identifier.
58func (c *Client) SetID(id string) {
59 c.id = id
60}
61
62// Path returns the client's instance filesystem path.
63func (c *Client) Path() string {
64 return c.path
65}
66
67// GetGlobalConfig retrieves the server's configuration via RPC.
68func (c *Client) GetGlobalConfig() (*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}