-
-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add cookies #507
Open
BorisP1234
wants to merge
1
commit into
minekube:master
Choose a base branch
from
BorisP1234:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat: add cookies #507
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Hi, hoping someone takes a look at the pr. To show an example of how someone would use cookies I created a couple functions which will be used in my multi proxy setup, communicating with Redis. I will be able to send players to any server under any proxy that uses this system. package transfer
import (
"context"
"errors"
"strings"
"sync"
"time"
"github.com/redis/go-redis/v9"
"github.com/team-vesperis/vesperis-mp/mp/database"
"github.com/team-vesperis/vesperis-mp/mp/share"
"go.minekube.com/common/minecraft/color"
"go.minekube.com/common/minecraft/component"
"go.minekube.com/common/minecraft/key"
"go.minekube.com/gate/pkg/edition/java/proxy"
"go.uber.org/zap"
)
var (
p *proxy.Proxy
logger *zap.SugaredLogger
pubsub *redis.PubSub
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
proxy_name string
transferKey key.Key
)
func InitializeTransfer(proxy *proxy.Proxy, log *zap.SugaredLogger, pn string) {
p = proxy
logger = log
proxy_name = pn
transferKey = key.New("vmp", "transfer")
ctx, cancel = context.WithCancel(context.Background())
go listenToTransfers()
logger.Info("Initialized transfer.")
}
// send players to other proxies
func OnPreShutdown() func(*proxy.PreShutdownEvent) {
return func(event *proxy.PreShutdownEvent) {
for _, player := range p.Players() {
for _, proxy := range share.GetAllProxyNames() {
if proxy != proxy_name {
err := TransferPlayerToProxy(player, proxy)
if err == nil {
return
}
}
}
player.Disconnect(&component.Text{
Content: "The proxy you were on has closed and there was no other proxy to connect to.",
S: component.Style{
Color: color.Red,
},
})
}
}
}
// check if player has cookie specifying which server he needs.
func OnChooseInitialServer() func(*proxy.PlayerChooseInitialServerEvent) {
return func(event *proxy.PlayerChooseInitialServerEvent) {
if len(p.Servers()) < 1 {
event.Player().Disconnect(&component.Text{
Content: "No available server. Please try again.",
S: component.Style{
Color: color.Red,
},
})
} else {
payload, err := event.Player().RequestCookieWithResult(transferKey)
if err == nil {
server_name := string(payload)
server := p.Server(server_name)
if server != nil {
event.SetInitialServer(server)
}
} else {
event.SetInitialServer(p.Servers()[0])
}
}
}
}
func TransferPlayerToServerOnOtherProxy(player proxy.Player, targetProxy string, targetServer string) error {
pubsub := database.GetRedisClient().Subscribe(context.Background(), "proxy_transfer_accept")
defer pubsub.Close()
err := database.GetRedisClient().Publish(context.Background(), "proxy_transfer_request", player.ID().String()+"|"+targetProxy+"|"+targetServer).Err()
if err != nil {
logger.Error("Error publishing transfer command: ", err)
return err
}
ch := pubsub.Channel()
timeout := time.After(2 * time.Second)
for {
select {
case msg := <-ch:
parts := strings.Split(msg.Payload, "|")
if len(parts) == 4 && parts[0] == player.ID().String() && parts[1] == targetProxy {
// server will be one of three things:
// 0, means the proxy is found but given server is not available
// 1, means the proxy is found and none server is specified
// 2, means the proxy is found and the given server is available
server := parts[3]
logger.Info(server)
if server == "0" {
logger.Warn("Specified server for player transfer was not found: ", player.ID().String())
return errors.New("specified server was not found")
}
if server == "2" {
// store cookie
payload := []byte(targetServer)
err := player.StoreCookie(transferKey, payload)
if err != nil {
logger.Warn("Could not store cookie for player transfer for: ", player.ID().String())
return errors.New("could not store cookie")
}
}
address := parts[2]
err := player.TransferToHost(address)
if err != nil {
return err
}
logger.Info("Player transfer successful: ", player.ID().String(), " to ", address)
return nil
}
case <-timeout:
logger.Warn("Timeout waiting for player transfer confirmation: ", player.ID().String())
return errors.New("timeout waiting for player transfer confirmation")
}
}
}
func TransferPlayerToProxy(player proxy.Player, targetProxy string) error {
return TransferPlayerToServerOnOtherProxy(player, targetProxy, "-")
}
func listenToTransfers() {
pubsub = database.GetRedisClient().Subscribe(ctx, "proxy_transfer_request")
for {
msg, err := pubsub.ReceiveMessage(ctx)
if err != nil {
if err == context.Canceled || ctx.Err() == context.Canceled {
return
}
logger.Error("Error receiving transfer command: ", err)
continue
}
wg.Add(1)
parts := strings.Split(msg.Payload, "|")
if len(parts) != 3 {
logger.Error("Invalid transfer command format")
continue
}
playerID := parts[0]
targetProxy := parts[1]
targetServer := parts[2]
address := p.Config().Bind
if targetProxy == proxy_name {
server := "0"
if targetServer != "-" {
server = "1"
} else {
foundServer := p.Server(targetServer)
if foundServer != nil {
server = "2"
}
}
logger.Info(server)
err := database.GetRedisClient().Publish(ctx, "proxy_transfer_accept", playerID+"|"+targetProxy+"|"+address+"|"+server).Err()
if err != nil {
logger.Warn("Error returning transfer. ", err)
}
}
wg.Done()
}
}
func CloseTransfer() {
logger.Info("Closing transfer...")
if cancel != nil {
cancel()
}
wg.Wait()
if pubsub != nil {
pubsub.Close()
logger.Info("Successfully closed transfer.")
}
} |
Thank you! I'll try to merge when I find the time |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Hello, cookie packets were introduced in Minecraft 1.20.5 along with transfer packets, an underrated feature that can be particularly useful in multi-proxy scenarios. That’s why I found it unfortunate that this functionality wasn’t included in the Gate API, so I decided to add it myself!
I added three functions to the
player
variable:player.CookieStore(key key.Key, payload []byte) error
This function sends a cookie to the player, storing it even between transfers until the player disconnects. It returns an error if:
player.CookieRequest(key key.Key) error
This function requests a cookie from the player. The player then sends a response packet, which can be handled with the
PlayerCookieResponseEvent
(explained later). It returns an error if:player.CookieRequestWithResult(key key.Key) ([]byte, error)
This function retrieves a cookie immediately instead of relying on
PlayerCookieResponseEvent
. It waits for a response from the player for up to 5 seconds. It returns an error if:I added a new event called
PlayerCookieResponseEvent
, which listens for cookie response packets. This event contains the player, key, and payload. If the requested cookie is not found, an empty payload is returned.You can test the features with this wonderful mod that I found called: CookieJar by Tis_awesomeness.