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