oauth_hyper.go

 1package dialog
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7
 8	tea "charm.land/bubbletea/v2"
 9	"github.com/charmbracelet/catwalk/pkg/catwalk"
10	"github.com/charmbracelet/crush/internal/config"
11	"github.com/charmbracelet/crush/internal/oauth/hyper"
12	"github.com/charmbracelet/crush/internal/ui/common"
13)
14
15func NewOAuthHyper(com *common.Common, provider catwalk.Provider, model config.SelectedModel, modelType config.SelectedModelType) (*OAuth, tea.Cmd) {
16	return newOAuth(com, provider, model, modelType, &OAuthHyper{})
17}
18
19type OAuthHyper struct {
20	cancelFunc func()
21}
22
23var _ OAuthProvider = (*OAuthHyper)(nil)
24
25func (m *OAuthHyper) name() string {
26	return "Hyper"
27}
28
29func (m *OAuthHyper) initiateAuth() tea.Msg {
30	minimumWait := 750 * time.Millisecond
31	startTime := time.Now()
32
33	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
34	defer cancel()
35
36	authResp, err := hyper.InitiateDeviceAuth(ctx)
37
38	ellapsed := time.Since(startTime)
39	if ellapsed < minimumWait {
40		time.Sleep(minimumWait - ellapsed)
41	}
42
43	if err != nil {
44		return ActionOAuthErrored{fmt.Errorf("failed to initiate device auth: %w", err)}
45	}
46
47	return ActionInitiateOAuth{
48		DeviceCode:      authResp.DeviceCode,
49		UserCode:        authResp.UserCode,
50		ExpiresIn:       authResp.ExpiresIn,
51		VerificationURL: authResp.VerificationURL,
52	}
53}
54
55func (m *OAuthHyper) startPolling(deviceCode string, expiresIn int) tea.Cmd {
56	return func() tea.Msg {
57		ctx, cancel := context.WithCancel(context.Background())
58		m.cancelFunc = cancel
59
60		refreshToken, err := hyper.PollForToken(ctx, deviceCode, expiresIn)
61		if err != nil {
62			if ctx.Err() != nil {
63				return nil
64			}
65			return ActionOAuthErrored{err}
66		}
67
68		token, err := hyper.ExchangeToken(ctx, refreshToken)
69		if err != nil {
70			return ActionOAuthErrored{fmt.Errorf("token exchange failed: %w", err)}
71		}
72
73		introspect, err := hyper.IntrospectToken(ctx, token.AccessToken)
74		if err != nil {
75			return ActionOAuthErrored{fmt.Errorf("token introspection failed: %w", err)}
76		}
77		if !introspect.Active {
78			return ActionOAuthErrored{fmt.Errorf("access token is not active")}
79		}
80
81		return ActionCompleteOAuth{token}
82	}
83}
84
85func (m *OAuthHyper) stopPolling() tea.Msg {
86	if m.cancelFunc != nil {
87		m.cancelFunc()
88	}
89	return nil
90}