@@ -5,24 +5,42 @@ import (
55 "errors"
66 "fmt"
77 "net"
8+ "net/http"
89 "time"
910
1011 "github.com/cenkalti/backoff/v4"
1112 "github.com/cli/cli/v2/internal/codespaces/api"
13+ "github.com/cli/cli/v2/internal/codespaces/connection"
1214 "github.com/cli/cli/v2/pkg/liveshare"
1315)
1416
15- func connectionReady (codespace * api.Codespace ) bool {
17+ func connectionReady (codespace * api.Codespace , usingDevTunnels bool ) bool {
18+ // If the codespace is not available, it is not ready
19+ if codespace .State != api .CodespaceStateAvailable {
20+ return false
21+ }
22+
23+ // If using Dev Tunnels, we need to check that we have all of the required tunnel properties
24+ if usingDevTunnels {
25+ return codespace .Connection .TunnelProperties .ConnectAccessToken != "" &&
26+ codespace .Connection .TunnelProperties .ManagePortsAccessToken != "" &&
27+ codespace .Connection .TunnelProperties .ServiceUri != "" &&
28+ codespace .Connection .TunnelProperties .TunnelId != "" &&
29+ codespace .Connection .TunnelProperties .ClusterId != "" &&
30+ codespace .Connection .TunnelProperties .Domain != ""
31+ }
32+
33+ // If not using Dev Tunnels, we need to check that we have all of the required Live Share properties
1634 return codespace .Connection .SessionID != "" &&
1735 codespace .Connection .SessionToken != "" &&
1836 codespace .Connection .RelayEndpoint != "" &&
19- codespace .Connection .RelaySAS != "" &&
20- codespace .State == api .CodespaceStateAvailable
37+ codespace .Connection .RelaySAS != ""
2138}
2239
2340type apiClient interface {
2441 GetCodespace (ctx context.Context , name string , includeConnection bool ) (* api.Codespace , error )
2542 StartCodespace (ctx context.Context , name string ) error
43+ HTTPClient () (* http.Client , error )
2644}
2745
2846type progressIndicator interface {
@@ -43,9 +61,48 @@ func (e *TimeoutError) Error() string {
4361 return e .message
4462}
4563
46- // ConnectToLiveshare waits for a Codespace to become running,
47- // and connects to it using a Live Share session.
64+ // GetCodespaceConnection waits until a codespace is able
65+ // to be connected to and initializes a connection to it.
66+ func GetCodespaceConnection (ctx context.Context , progress progressIndicator , apiClient apiClient , codespace * api.Codespace ) (* connection.CodespaceConnection , error ) {
67+ codespace , err := waitUntilCodespaceConnectionReady (ctx , progress , apiClient , codespace , true )
68+ if err != nil {
69+ return nil , err
70+ }
71+
72+ progress .StartProgressIndicatorWithLabel ("Connecting to codespace" )
73+ defer progress .StopProgressIndicator ()
74+
75+ httpClient , err := apiClient .HTTPClient ()
76+ if err != nil {
77+ return nil , fmt .Errorf ("error getting http client: %w" , err )
78+ }
79+
80+ return connection .NewCodespaceConnection (ctx , codespace , httpClient )
81+ }
82+
83+ // ConnectToLiveshare waits until a codespace is able to be
84+ // connected to and connects to it using a Live Share session.
4885func ConnectToLiveshare (ctx context.Context , progress progressIndicator , sessionLogger logger , apiClient apiClient , codespace * api.Codespace ) (* liveshare.Session , error ) {
86+ codespace , err := waitUntilCodespaceConnectionReady (ctx , progress , apiClient , codespace , false )
87+ if err != nil {
88+ return nil , err
89+ }
90+
91+ progress .StartProgressIndicatorWithLabel ("Connecting to codespace" )
92+ defer progress .StopProgressIndicator ()
93+
94+ return liveshare .Connect (ctx , liveshare.Options {
95+ SessionID : codespace .Connection .SessionID ,
96+ SessionToken : codespace .Connection .SessionToken ,
97+ RelaySAS : codespace .Connection .RelaySAS ,
98+ RelayEndpoint : codespace .Connection .RelayEndpoint ,
99+ HostPublicKeys : codespace .Connection .HostPublicKeys ,
100+ Logger : sessionLogger ,
101+ })
102+ }
103+
104+ // waitUntilCodespaceConnectionReady waits for a Codespace to be running and is able to be connected to.
105+ func waitUntilCodespaceConnectionReady (ctx context.Context , progress progressIndicator , apiClient apiClient , codespace * api.Codespace , usingDevTunnels bool ) (* api.Codespace , error ) {
49106 if codespace .State != api .CodespaceStateAvailable {
50107 progress .StartProgressIndicatorWithLabel ("Starting codespace" )
51108 defer progress .StopProgressIndicator ()
@@ -54,7 +111,7 @@ func ConnectToLiveshare(ctx context.Context, progress progressIndicator, session
54111 }
55112 }
56113
57- if ! connectionReady (codespace ) {
114+ if ! connectionReady (codespace , usingDevTunnels ) {
58115 expBackoff := backoff .NewExponentialBackOff ()
59116 expBackoff .Multiplier = 1.1
60117 expBackoff .MaxInterval = 10 * time .Second
@@ -67,7 +124,7 @@ func ConnectToLiveshare(ctx context.Context, progress progressIndicator, session
67124 return backoff .Permanent (fmt .Errorf ("error getting codespace: %w" , err ))
68125 }
69126
70- if connectionReady (codespace ) {
127+ if connectionReady (codespace , usingDevTunnels ) {
71128 return nil
72129 }
73130
@@ -83,17 +140,7 @@ func ConnectToLiveshare(ctx context.Context, progress progressIndicator, session
83140 }
84141 }
85142
86- progress .StartProgressIndicatorWithLabel ("Connecting to codespace" )
87- defer progress .StopProgressIndicator ()
88-
89- return liveshare .Connect (ctx , liveshare.Options {
90- SessionID : codespace .Connection .SessionID ,
91- SessionToken : codespace .Connection .SessionToken ,
92- RelaySAS : codespace .Connection .RelaySAS ,
93- RelayEndpoint : codespace .Connection .RelayEndpoint ,
94- HostPublicKeys : codespace .Connection .HostPublicKeys ,
95- Logger : sessionLogger ,
96- })
143+ return codespace , nil
97144}
98145
99146// ListenTCP starts a localhost tcp listener on 127.0.0.1 (unless allInterfaces is true) and returns the listener and bound port
0 commit comments