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}