From 55c77b13e4408fe7820998c34f4669485a30c73c Mon Sep 17 00:00:00 2001 From: rektdeckard Date: Fri, 19 Jul 2024 11:21:27 -0600 Subject: [PATCH] feat(cmd): complete poll logic and prettify with a spinner --- cmd/lk/auth.go | 97 +++++++++++++++++++++++++++++++++----------------- go.mod | 1 + go.sum | 2 ++ 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/cmd/lk/auth.go b/cmd/lk/auth.go index adcc97e1..9f153d7c 100644 --- a/cmd/lk/auth.go +++ b/cmd/lk/auth.go @@ -23,6 +23,7 @@ import ( "net/url" "time" + "github.com/charmbracelet/huh/spinner" "github.com/urfave/cli/v3" ) @@ -36,6 +37,43 @@ const ( claimSessionEndpoint = "/cli/claim" ) +var ( + disconnect bool + timeout int64 + interval int64 + authClient AuthClient + AuthCommands = []*cli.Command{ + { + Name: "auth", + Usage: "Authenticate the CLI via the browser to permit advanced actions", + Category: "Core", + Before: createAuthClient, + Action: handleAuth, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "d", + Aliases: []string{"disconnect"}, + Destination: &disconnect, + }, + &cli.IntFlag{ + Name: "t", + Aliases: []string{"timeout"}, + Usage: "Number of `SECONDS` to attempt authentication before giving up", + Destination: &timeout, + Value: 60, + }, + &cli.IntFlag{ + Name: "i", + Aliases: []string{"poll-interval"}, + Usage: "Number of `SECONDS` between poll requests to verify authentication", + Destination: &interval, + Value: 4, + }, + }, + }, + } +) + type CreateTokenResponse struct { Identifier string Token string @@ -78,7 +116,7 @@ func (a *AuthClient) GetVerificationToken(subdomain string) (*CreateTokenRespons return &a.verificationToken, nil } -func (a *AuthClient) ClaimSession() (*CreateTokenResponse, error) { +func (a *AuthClient) ClaimSession(ctx context.Context) (*CreateTokenResponse, error) { if a.verificationToken.Token == "" || time.Now().Unix() > a.verificationToken.Expires { return nil, errors.New("session expired") } @@ -92,11 +130,20 @@ func (a *AuthClient) ClaimSession() (*CreateTokenResponse, error) { params.Add("t", a.verificationToken.Token) reqURL.RawQuery = params.Encode() - resp, err := a.client.Get(reqURL.String()) + req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil) + if err != nil { + return nil, err + } + resp, err := a.client.Do(req) if err != nil { return nil, err } + if resp.StatusCode == http.StatusUnauthorized { + // Not yet approved + return nil, nil + } + sessionToken := &CreateTokenResponse{} err = json.NewDecoder(resp.Body).Decode(&sessionToken) if err != nil { @@ -121,27 +168,6 @@ func NewAuthClient(client *http.Client, baseURL string) *AuthClient { return a } -var ( - disconnect bool - authClient AuthClient - AuthCommands = []*cli.Command{ - { - Name: "auth", - Usage: "Add or remove projects and view existing project properties", - Category: "Core", - Before: createAuthClient, - Action: handleAuth, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "d", - Aliases: []string{"disconnect"}, - Destination: &disconnect, - }, - }, - }, - } -) - func createAuthClient(ctx context.Context, cmd *cli.Command) error { if err := loadProjectConfig(ctx, cmd); err != nil { return err @@ -179,30 +205,37 @@ func tryAuthIfNeeded(ctx context.Context, cmd *cli.Command) error { params.Add("t", token.Token) authURL.RawQuery = params.Encode() - fmt.Println(authURL) + fmt.Printf("Please confirm access by visiting:\n\n %s\n\n", authURL.String()) + + if err := spinner.New(). + Title("Awaiting confirmation..."). + Action(func() { err = pollClaim(ctx, cmd) }). + Run(); err != nil { + return err + } - return pollClaim(ctx, cmd) + return err } -func pollClaim(context.Context, *cli.Command) error { +func pollClaim(ctx context.Context, _ *cli.Command) error { claim := make(chan *CreateTokenResponse) cancel := make(chan error) go func() { for { - fmt.Println("Polling...") - time.Sleep(10 * time.Second) - session, err := authClient.ClaimSession() + time.Sleep(time.Duration(interval) * time.Second) + session, err := authClient.ClaimSession(ctx) if err != nil { cancel <- err return } - fmt.Println(session) - claim <- session + if session != nil { + claim <- session + } } }() select { - case <-time.After(1 * time.Minute): + case <-time.After(time.Duration(timeout) * time.Second): return errors.New("session claim timed out") case err := <-cancel: return err diff --git a/go.mod b/go.mod index 96968d58..0e74cbb5 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbles v0.18.0 // indirect github.com/charmbracelet/bubbletea v0.26.4 // indirect + github.com/charmbracelet/huh/spinner v0.0.0-20240714135825-43e9eb5aeab6 // indirect github.com/charmbracelet/x/ansi v0.1.4 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240617190524-788ec55faed1 // indirect github.com/charmbracelet/x/input v0.1.2 // indirect diff --git a/go.sum b/go.sum index 83f436b6..fbcc29b1 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkim github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0= github.com/charmbracelet/huh v0.5.1 h1:t5j6g9sMjAE2a9AQuc4lNL7pf/0X4WdHiiMGkL8v/aM= github.com/charmbracelet/huh v0.5.1/go.mod h1:gs7b2brpzXkY0PBWUqJrlzvOowTCL0vNAR6OTItc+kA= +github.com/charmbracelet/huh/spinner v0.0.0-20240714135825-43e9eb5aeab6 h1:Wey8Un1qEEHI18y0HOs3K2HBBb9aEssoDmQo1+Idpuo= +github.com/charmbracelet/huh/spinner v0.0.0-20240714135825-43e9eb5aeab6/go.mod h1:CrXBZnOWs3zpyppOZZS7lu2CpLq2jx6U5chL/frRG/E= github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=