Skip to content

Commit 2a8e179

Browse files
feat: Cookies API (#535)
* feat: add cookies * refactor: rework cookie system * refactor: rename variables for clarity * refactor: use sync.Map and update to 1.21.5 * refactor: enhance cookie handling and viewer context management - Introduced context support in the Viewer interface for better management of player connections. - Updated cookie storage and request handling to include validation for player protocol and state. - Replaced deprecated methods with new implementations for improved clarity and functionality. - Enhanced event handling for cookie responses, allowing for more robust interaction with client requests. - Removed obsolete cookie handling code to streamline the system. * feat: implement cookie storage and request handling - Added StoreCookie and RequestCookie RPC methods to the GateService for managing cookies on players' clients. - Introduced corresponding request and response message types for cookie operations in the API. - Updated documentation to reflect new cookie handling features. - Enhanced event handling for cookie requests and storage, ensuring proper player context management. * add test and format code --------- Co-authored-by: BorisP1234 <[email protected]> Co-authored-by: Boris <[email protected]>
1 parent 18eda21 commit 2a8e179

30 files changed

+1387
-343
lines changed

.web/docs/developers/api/gen/definition.gen.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
- [Player](#minekube-gate-v1-Player)
1818
- [RegisterServerRequest](#minekube-gate-v1-RegisterServerRequest)
1919
- [RegisterServerResponse](#minekube-gate-v1-RegisterServerResponse)
20+
- [RequestCookieRequest](#minekube-gate-v1-RequestCookieRequest)
21+
- [RequestCookieResponse](#minekube-gate-v1-RequestCookieResponse)
2022
- [Server](#minekube-gate-v1-Server)
23+
- [StoreCookieRequest](#minekube-gate-v1-StoreCookieRequest)
24+
- [StoreCookieResponse](#minekube-gate-v1-StoreCookieResponse)
2125
- [UnregisterServerRequest](#minekube-gate-v1-UnregisterServerRequest)
2226
- [UnregisterServerResponse](#minekube-gate-v1-UnregisterServerResponse)
2327

@@ -222,6 +226,37 @@ RegisterServerResponse is the response for RegisterServer method.
222226

223227

224228

229+
<a name="minekube-gate-v1-RequestCookieRequest"></a>
230+
231+
### RequestCookieRequest
232+
RequestCookieRequest is the request for RequestCookie method.
233+
234+
235+
| Field | Type | Label | Description |
236+
| ----- | ---- | ----- | ----------- |
237+
| player | [string](#string) | | The player&#39;s username or ID |
238+
| key | [string](#string) | | The key of the cookie in format `namespace:key` |
239+
240+
241+
242+
243+
244+
245+
<a name="minekube-gate-v1-RequestCookieResponse"></a>
246+
247+
### RequestCookieResponse
248+
RequestCookieResponse is the response for RequestCookie method.
249+
250+
251+
| Field | Type | Label | Description |
252+
| ----- | ---- | ----- | ----------- |
253+
| payload | [bytes](#bytes) | | The payload of the cookie. May be empty if the cookie is not found. |
254+
255+
256+
257+
258+
259+
225260
<a name="minekube-gate-v1-Server"></a>
226261

227262
### Server
@@ -239,6 +274,33 @@ Server represents a backend server where Gate can connect players to.
239274

240275

241276

277+
<a name="minekube-gate-v1-StoreCookieRequest"></a>
278+
279+
### StoreCookieRequest
280+
StoreCookieRequest is the request for StoreCookie method.
281+
282+
283+
| Field | Type | Label | Description |
284+
| ----- | ---- | ----- | ----------- |
285+
| player | [string](#string) | | The player&#39;s username or ID |
286+
| key | [string](#string) | | The key of the cookie in format `namespace:key` |
287+
| payload | [bytes](#bytes) | | The payload to store. Passing an empty payload will remove the cookie. |
288+
289+
290+
291+
292+
293+
294+
<a name="minekube-gate-v1-StoreCookieResponse"></a>
295+
296+
### StoreCookieResponse
297+
StoreCookieResponse is the response for StoreCookie method.
298+
299+
300+
301+
302+
303+
242304
<a name="minekube-gate-v1-UnregisterServerRequest"></a>
243305

244306
### UnregisterServerRequest
@@ -287,6 +349,8 @@ All methods follow standard gRPC error codes and include detailed error messages
287349
| UnregisterServer | [UnregisterServerRequest](#minekube-gate-v1-UnregisterServerRequest) | [UnregisterServerResponse](#minekube-gate-v1-UnregisterServerResponse) | UnregisterServer removes a server from the proxy. Returns NOT_FOUND if no matching server is found. Returns INVALID_ARGUMENT if neither name nor address is provided. |
288350
| ConnectPlayer | [ConnectPlayerRequest](#minekube-gate-v1-ConnectPlayerRequest) | [ConnectPlayerResponse](#minekube-gate-v1-ConnectPlayerResponse) | ConnectPlayer connects a player to a specified server. Returns NOT_FOUND if either the player or target server doesn&#39;t exist. Returns FAILED_PRECONDITION if the connection attempt fails. |
289351
| DisconnectPlayer | [DisconnectPlayerRequest](#minekube-gate-v1-DisconnectPlayerRequest) | [DisconnectPlayerResponse](#minekube-gate-v1-DisconnectPlayerResponse) | DisconnectPlayer disconnects a player from the proxy. Returns NOT_FOUND if the player doesn&#39;t exist. Returns INVALID_ARGUMENT if the reason text is malformed. |
352+
| StoreCookie | [StoreCookieRequest](#minekube-gate-v1-StoreCookieRequest) | [StoreCookieResponse](#minekube-gate-v1-StoreCookieResponse) | StoreCookie stores a cookie on a player&#39;s client. Returns NOT_FOUND if the player doesn&#39;t exist. Passing an empty payload will remove the cookie. |
353+
| RequestCookie | [RequestCookieRequest](#minekube-gate-v1-RequestCookieRequest) | [RequestCookieResponse](#minekube-gate-v1-RequestCookieResponse) | RequestCookie requests a cookie from a player&#39;s client. The payload in RequestCookieResponse may be empty if the cookie is not found. |
290354

291355

292356

api/minekube/gate/v1/gate_service.proto

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,47 @@ service GateService {
3737
// Returns NOT_FOUND if the player doesn't exist.
3838
// Returns INVALID_ARGUMENT if the reason text is malformed.
3939
rpc DisconnectPlayer(DisconnectPlayerRequest) returns (DisconnectPlayerResponse);
40+
41+
// StoreCookie stores a cookie on a player's client.
42+
// Returns NOT_FOUND if the player doesn't exist.
43+
// Passing an empty payload will remove the cookie.
44+
rpc StoreCookie(StoreCookieRequest) returns (StoreCookieResponse);
45+
46+
// RequestCookie requests a cookie from a player's client.
47+
// The payload in RequestCookieResponse may be empty if the cookie is not found.
48+
rpc RequestCookie(RequestCookieRequest) returns (RequestCookieResponse);
4049
}
4150

51+
// StoreCookieRequest is the request for StoreCookie method.
52+
message StoreCookieRequest {
53+
// The player's username or ID
54+
string player = 1;
55+
// The key of the cookie in format `namespace:key`
56+
string key = 2;
57+
// The payload to store.
58+
// Passing an empty payload will remove the cookie.
59+
bytes payload = 3;
60+
}
61+
62+
// StoreCookieResponse is the response for StoreCookie method.
63+
message StoreCookieResponse {}
64+
65+
// RequestCookieRequest is the request for RequestCookie method.
66+
message RequestCookieRequest {
67+
// The player's username or ID
68+
string player = 1;
69+
// The key of the cookie in format `namespace:key`
70+
string key = 2;
71+
}
72+
73+
// RequestCookieResponse is the response for RequestCookie method.
74+
message RequestCookieResponse {
75+
// The payload of the cookie.
76+
// May be empty if the cookie is not found.
77+
bytes payload = 1;
78+
}
79+
80+
4281
// DisconnectPlayerRequest is the request for DisconnectPlayer method.
4382
message DisconnectPlayerRequest {
4483
// The player's username or ID to disconnect

pkg/edition/java/bossbar/bossbar.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
package bossbar
33

44
import (
5+
"context"
6+
57
"go.minekube.com/common/minecraft/component"
68
packet "go.minekube.com/gate/pkg/edition/java/proto/packet/bossbar"
79
"go.minekube.com/gate/pkg/edition/java/proto/packet/chat"
@@ -79,6 +81,7 @@ func RemoveAllViewers(b BossBar) {
7981
// Viewer is the interface for a boss bar viewer (e.g. a player).
8082
type Viewer interface {
8183
ID() uuid.UUID
84+
Context() context.Context
8285
proto.PacketWriter
8386
}
8487

pkg/edition/java/bossbar/internal.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package bossbar
22

33
import (
44
"context"
5-
"go.minekube.com/gate/pkg/edition/java/proto/packet/chat"
65
"sync"
76

7+
"go.minekube.com/gate/pkg/edition/java/proto/packet/chat"
8+
89
"go.minekube.com/common/minecraft/component"
9-
"go.minekube.com/gate/pkg/edition/java/internal/protoutil"
10+
"go.minekube.com/gate/pkg/edition/java/internal/methods"
1011
"go.minekube.com/gate/pkg/edition/java/proto/packet/bossbar"
1112
"go.minekube.com/gate/pkg/edition/java/proto/version"
1213
"go.minekube.com/gate/pkg/gate/proto"
@@ -78,17 +79,14 @@ func (b *bossBar) AddViewer(viewer Viewer) error {
7879

7980
func newBarViewer(viewer Viewer, bar *bossBar) *barViewer {
8081
removedCtx, cancel := context.WithCancel(context.Background())
81-
if c, ok := viewer.(interface{ Context() context.Context }); ok {
82-
viewerCtx := c.Context()
83-
go func() {
84-
defer cancel()
85-
select {
86-
case <-viewerCtx.Done():
87-
_ = bar.RemoveViewer(viewer)
88-
case <-removedCtx.Done(): // viewer removed by RemoveViewer method
89-
}
90-
}()
91-
}
82+
go func() {
83+
defer cancel()
84+
select {
85+
case <-viewer.Context().Done():
86+
_ = bar.RemoveViewer(viewer)
87+
case <-removedCtx.Done(): // viewer removed by RemoveViewer method
88+
}
89+
}()
9290
return &barViewer{
9391
Viewer: viewer,
9492
ctx: removedCtx,
@@ -252,7 +250,7 @@ func toSlice(m map[uuid.UUID]*barViewer) []Viewer {
252250
// isProtocolSupported returns true the viewer's protocol supports boss bar.
253251
func isProtocolSupported(viewer Viewer) bool {
254252
// below 1.9 doesn't support boss bars
255-
p, ok := protoutil.Protocol(viewer)
253+
p, ok := methods.Protocol(viewer)
256254
if !ok {
257255
// assume supported
258256
return true

pkg/edition/java/cookie/cookie.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package cookie
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"time"
8+
9+
"github.com/robinbraemer/event"
10+
"go.minekube.com/common/minecraft/key"
11+
"go.minekube.com/gate/pkg/edition/java/proto/packet/cookie"
12+
"go.minekube.com/gate/pkg/edition/java/proto/state/states"
13+
"go.minekube.com/gate/pkg/gate/proto"
14+
)
15+
16+
var (
17+
ErrUnsupportedClientProtocol = errors.New("player version must be at least 1.20.5 to use cookies")
18+
ErrUnsupportedState = fmt.Errorf("cookie can only be stored in %s or %s state", states.ConfigState, states.PlayState)
19+
)
20+
21+
const (
22+
MaxPayloadSize = cookie.MaxPayloadSize
23+
DefaultRequestTimeout = 10 * time.Second
24+
)
25+
26+
// Cookie is cookie that can be sent and received from clients.
27+
//
28+
// Use Store to save a cookie on the client.
29+
// When the cookie is modified, Store must be called again to save the changes.
30+
//
31+
// The maximum size of a cookie is 5 kiB.
32+
type Cookie struct {
33+
Key key.Key
34+
Payload []byte
35+
}
36+
37+
// Client is a player that can store and send stored cookies.
38+
type Client interface {
39+
proto.PacketWriter
40+
Context() context.Context
41+
}
42+
43+
// Store stores a cookie on the player's client.
44+
//
45+
// If the player's protocol is below 1.20.5, ErrUnsupportedClientProtocol is returned.
46+
// If the player's state is not states.ConfigState or states.PlayState, ErrUnsupportedState is returned.
47+
func Store(c Client, cookie *Cookie) error {
48+
return store(c, cookie)
49+
}
50+
51+
// Clear clears a stored cookie from the player's client.
52+
// This is a helper function that stores a cookie with an empty payload.
53+
//
54+
// If the player's protocol is below 1.20.5, ErrUnsupportedClientProtocol is returned.
55+
// If the player's state is not states.ConfigState or states.PlayState, ErrUnsupportedState is returned.
56+
func Clear(c Client, key key.Key) error {
57+
return store(c, &Cookie{Key: key, Payload: nil})
58+
}
59+
60+
// Request requests a stored cookie from the player's client by a given key.
61+
// This is a blocking operation and the context should be used to timeout the request.
62+
// The DefaultRequestTimeout always applies as a safety net.
63+
//
64+
// The event manager is used to listen for the cookie response event fired by Gate.
65+
// Use proxy.Event() to get the proxy's event manager.
66+
//
67+
// Request only subscribes to the cookie response event only until Request returns.
68+
func Request(ctx context.Context, c Client, key key.Key, eventMgr event.Manager) (*Cookie, error) {
69+
return request(ctx, c, key, eventMgr)
70+
}
71+
72+
// RequestAndForget works like Request but does not wait for a response.
73+
//
74+
// The cookie response event will still be fired by Gate and can be listened to in the event manager.
75+
func RequestAndForget(c Client, key key.Key) error {
76+
return requestAndForget(c, key)
77+
}

0 commit comments

Comments
 (0)