@@ -3,6 +3,7 @@ package realms
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "io"
89 "net/http"
@@ -19,6 +20,13 @@ type Client struct {
1920 httpClient * http.Client
2021}
2122
23+ const realmsBaseURL = "https://pocket.realms.minecraft.net"
24+
25+ var (
26+ ErrPlayerNotInRealm = errors .New ("player not in realm" )
27+ ErrRealmNotFound = errors .New ("realm not found" )
28+ )
29+
2230// NewClient returns a new Client instance with the supplied token source for authentication.
2331// If httpClient is nil, http.DefaultClient will be used to request the realms api.
2432func NewClient (src oauth2.TokenSource , httpClient * http.Client ) * Client {
@@ -98,37 +106,65 @@ type Realm struct {
98106 client * Client
99107}
100108
101- // Address requests the address and port to connect to this realm from the api,
102- // will wait for the realm to start if it is currently offline.
103- func (r * Realm ) Address (ctx context.Context ) (address string , err error ) {
109+ // RealmAddress contains the address returned by the Realms join endpoint along
110+ // with the signalling protocol used for connecting to it.
111+ type RealmAddress struct {
112+ NetworkProtocol string `json:"networkProtocol"`
113+ Address string `json:"address"`
114+ }
115+
116+ // Address requests the address and protocol used to connect to this realm.
117+ // It will wait for the realm to start if it is currently offline.
118+ func (r * Realm ) Address (ctx context.Context ) (RealmAddress , error ) {
119+ if r .client == nil {
120+ return RealmAddress {}, fmt .Errorf ("realm client is nil" )
121+ }
122+ return r .client .RealmAddress (ctx , r .ID )
123+ }
124+
125+ // RealmAddress requests the address and protocol used to connect to a realm
126+ // from the api, and waits for the realm to start if it is currently offline.
127+ func (r * Client ) RealmAddress (ctx context.Context , realmID int ) (RealmAddress , error ) {
104128 ticker := time .NewTicker (time .Second * 3 )
105129 defer ticker .Stop ()
106- for range ticker .C {
107- body , status , err := r .client .request (ctx , fmt .Sprintf ("/worlds/%d/join" , r .ID ))
108- if err != nil {
109- if status == 503 && ctx .Err () == nil {
110- continue
130+ for {
131+ select {
132+ case <- ctx .Done ():
133+ return RealmAddress {}, ctx .Err ()
134+ case <- ticker .C :
135+ body , status , err := r .request (ctx , fmt .Sprintf ("/worlds/%d/join" , realmID ))
136+ if err != nil {
137+ switch status {
138+ case 503 :
139+ continue
140+ case 404 :
141+ return RealmAddress {}, ErrRealmNotFound
142+ case 403 :
143+ return RealmAddress {}, ErrPlayerNotInRealm
144+ }
145+ return RealmAddress {}, err
111146 }
112- return "" , err
113- }
114147
115- var data struct {
116- Address string `json:"address"`
117- PendingUpdate bool `json:"pendingUpdate"`
118- }
119- if err := json .Unmarshal (body , & data ); err != nil {
120- return "" , err
148+ var address RealmAddress
149+ if err := json .Unmarshal (body , & address ); err != nil {
150+ return RealmAddress {}, err
151+ }
152+ return address , nil
121153 }
122- return data .Address , nil
123154 }
124- panic ("unreachable" )
125155}
126156
127157// OnlinePlayers gets all the players currently on this realm,
128158// Returns a 403 error if the current user is not the owner of the Realm.
129159func (r * Realm ) OnlinePlayers (ctx context.Context ) (players []Player , err error ) {
130- body , _ , err := r .client .request (ctx , fmt .Sprintf ("/worlds/%d" , r .ID ))
160+ body , status , err := r .client .request (ctx , fmt .Sprintf ("/worlds/%d" , r .ID ))
131161 if err != nil {
162+ switch status {
163+ case 403 :
164+ return nil , ErrPlayerNotInRealm
165+ case 404 :
166+ return nil , ErrRealmNotFound
167+ }
132168 return nil , err
133169 }
134170
@@ -142,25 +178,31 @@ func (r *Realm) OnlinePlayers(ctx context.Context) (players []Player, err error)
142178
143179// xboxToken returns the xbox token used for the api.
144180func (r * Client ) xboxToken (ctx context.Context ) (* auth.XBLToken , error ) {
145- if r .xblToken != nil {
181+ if r .xblToken != nil && r . xblToken . Valid () {
146182 return r .xblToken , nil
147183 }
184+ if r .tokenSrc == nil {
185+ return nil , fmt .Errorf ("token source is nil" )
186+ }
148187
149188 t , err := r .tokenSrc .Token ()
150189 if err != nil {
151190 return nil , err
152191 }
153192
154- r .xblToken , err = auth .RequestXBLToken (ctx , t , "https://pocket.realms.minecraft.net /" )
193+ r .xblToken , err = auth .RequestXBLToken (ctx , t , realmsBaseURL + " /" )
155194 return r .xblToken , err
156195}
157196
158197// request sends an http get request to path with the right headers for the api set.
159198func (r * Client ) request (ctx context.Context , path string ) (body []byte , status int , err error ) {
160- if string (path [0 ]) != "/" {
199+ if path == "" {
200+ return nil , 0 , fmt .Errorf ("path is empty" )
201+ }
202+ if path [0 ] != '/' {
161203 path = "/" + path
162204 }
163- req , err := http .NewRequestWithContext (ctx , "GET" , fmt . Sprintf ( "https://pocket.realms.minecraft.net%s" , path ) , nil )
205+ req , err := http .NewRequestWithContext (ctx , "GET" , realmsBaseURL + path , nil )
164206 if err != nil {
165207 return nil , 0 , err
166208 }
0 commit comments