Skip to content

Commit 40538b1

Browse files
realms/realms.go: Add realms NetworkProtocol field and clean up handling (#414)
1 parent d4e8a5a commit 40538b1

1 file changed

Lines changed: 65 additions & 23 deletions

File tree

minecraft/realms/realms.go

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package realms
33
import (
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.
2432
func 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.
129159
func (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.
144180
func (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.
159198
func (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

Comments
 (0)