diff --git a/app.go b/app.go index 607f0fd..9ee65eb 100644 --- a/app.go +++ b/app.go @@ -14,8 +14,7 @@ import ( sunriseLib "github.com/nathan-osman/go-sunrise" "github.com/Workiva/go-datastructures/queue" - "github.com/Xevion/gome-assistant/internal" - "github.com/Xevion/gome-assistant/internal/http" + internal "github.com/Xevion/gome-assistant/internal" "github.com/Xevion/gome-assistant/internal/parse" ws "github.com/Xevion/gome-assistant/internal/websocket" ) @@ -30,7 +29,7 @@ type App struct { // Wraps the ws connection with added mutex locking wsWriter *ws.WebsocketWriter - httpClient *http.HttpClient + httpClient *internal.HttpClient service *Service state *StateImpl @@ -148,19 +147,8 @@ func NewApp(request NewAppRequest) (*App, error) { var err error baseURL, err = url.Parse(request.URL) if err != nil { - return nil, ErrInvalidArgs + return nil, fmt.Errorf("failed to parse URL: %w", err) } - } else { - // This is deprecated and will be removed in a future release - port := request.Port - if port == "" { - port = "8123" - } - baseURL.Scheme = "http" - if request.Secure { - baseURL.Scheme = "https" - } - baseURL.Host = request.IpAddress + ":" + port } conn, ctx, ctxCancel, err := ws.ConnectionFromUri(baseURL, request.HAAuthToken) @@ -171,7 +159,7 @@ func NewApp(request NewAppRequest) (*App, error) { return nil, err } - httpClient := http.NewHttpClient(baseURL, request.HAAuthToken) + httpClient := internal.NewHttpClient(baseURL, request.HAAuthToken) wsWriter := &ws.WebsocketWriter{Conn: conn} service := newService(wsWriter) diff --git a/go.mod b/go.mod index 527f19e..5325562 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/nathan-osman/go-sunrise v1.1.0 github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 + resty.dev/v3 v3.0.0-beta.3 ) require ( @@ -19,4 +20,5 @@ require ( github.com/joho/godotenv v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect + golang.org/x/net v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index 0fdb7cd..bb11694 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -109,3 +111,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E= +resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4= diff --git a/internal/http.go b/internal/http.go new file mode 100644 index 0000000..2b894a3 --- /dev/null +++ b/internal/http.go @@ -0,0 +1,74 @@ +// http is used to interact with the home assistant +// REST API. Currently only used to retrieve state for +// a single entity_id +package internal + +import ( + "errors" + "net/url" + "time" + + "resty.dev/v3" +) + +type HttpClient struct { + client *resty.Client +} + +func NewHttpClient(url *url.URL, token string) *HttpClient { + // Shallow copy the URL to avoid modifying the original + u := *url + u.Path = "/api" + if u.Scheme == "ws" { + u.Scheme = "http" + } + if u.Scheme == "wss" { + u.Scheme = "https" + } + + // Create resty client with configuration + client := resty.New(). + SetBaseURL(u.String()). + SetHeader("Authorization", "Bearer "+token). + SetTimeout(30 * time.Second). + SetRetryCount(3). + SetRetryWaitTime(1 * time.Second). + SetRetryMaxWaitTime(5 * time.Second). + AddRetryConditions(func(r *resty.Response, err error) bool { + return err != nil || r.StatusCode() >= 500 + }) + + return &HttpClient{ + client: client, + } +} + +func (c *HttpClient) GetState(entityId string) ([]byte, error) { + resp, err := c.client.R(). + Get("/states/" + entityId) + + if err != nil { + return nil, errors.New("Error making HTTP request: " + err.Error()) + } + + if resp.StatusCode() >= 400 { + return nil, errors.New("HTTP error: " + resp.Status() + " - " + string(resp.Bytes())) + } + + return resp.Bytes(), nil +} + +func (c *HttpClient) States() ([]byte, error) { + resp, err := c.client.R(). + Get("/states") + + if err != nil { + return nil, errors.New("Error making HTTP request: " + err.Error()) + } + + if resp.StatusCode() >= 400 { + return nil, errors.New("HTTP error: " + resp.Status() + " - " + string(resp.Bytes())) + } + + return resp.Bytes(), nil +} diff --git a/internal/http/http.go b/internal/http/http.go deleted file mode 100644 index b9da9a1..0000000 --- a/internal/http/http.go +++ /dev/null @@ -1,72 +0,0 @@ -// http is used to interact with the home assistant -// REST API. Currently only used to retrieve state for -// a single entity_id -package http - -import ( - "errors" - "io" - "net/http" - "net/url" -) - -type HttpClient struct { - url string - token string -} - -func NewHttpClient(url *url.URL, token string) *HttpClient { - // Shallow copy the URL to avoid modifying the original - u := *url - u.Path = "/api" - if u.Scheme == "ws" { - u.Scheme = "http" - } - if u.Scheme == "wss" { - u.Scheme = "https" - } - - return &HttpClient{ - url: u.String(), - token: token, - } -} - -func (c *HttpClient) GetState(entityId string) ([]byte, error) { - resp, err := get(c.url+"/states/"+entityId, c.token) - if err != nil { - return nil, err - } - return resp, nil -} - -func (c *HttpClient) States() ([]byte, error) { - resp, err := get(c.url+"/states", c.token) - if err != nil { - return nil, err - } - return resp, nil -} - -func get(url, token string) ([]byte, error) { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, errors.New("Error creating HTTP request: " + err.Error()) - } - - req.Header.Add("Authorization", "Bearer "+token) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, errors.New("Error on response.\n[ERROR] -" + err.Error()) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errors.New("Error while reading the response bytes:" + err.Error()) - } - - return body, nil -} diff --git a/state.go b/state.go index 0da3d7f..5c00d9d 100644 --- a/state.go +++ b/state.go @@ -8,7 +8,7 @@ import ( "github.com/golang-module/carbon" - "github.com/Xevion/gome-assistant/internal/http" + internal "github.com/Xevion/gome-assistant/internal" ) type State interface { @@ -23,7 +23,7 @@ type State interface { // State is used to retrieve state from Home Assistant. type StateImpl struct { - httpClient *http.HttpClient + httpClient *internal.HttpClient latitude float64 longitude float64 } @@ -35,7 +35,7 @@ type EntityState struct { LastChanged time.Time `json:"last_changed"` } -func newState(c *http.HttpClient, homeZoneEntityId string) (*StateImpl, error) { +func newState(c *internal.HttpClient, homeZoneEntityId string) (*StateImpl, error) { state := &StateImpl{httpClient: c} // Ensure the zone exists and has required attributes