From 98dc1334978683bb0bec263fe809a70e3ec00e91 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sat, 5 Jul 2025 17:13:50 +0300 Subject: [PATCH] feat(Session): resume gateway url (#1307) --- event.go | 3 +++ events.go | 15 ++++++++------- structs.go | 3 +++ wsapi.go | 45 +++++++++++++++++++++++++++++++-------------- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/event.go b/event.go index 84dbdc7..6e6c500 100644 --- a/event.go +++ b/event.go @@ -244,4 +244,7 @@ func (s *Session) onReady(r *Ready) { // Store the SessionID within the Session struct. s.sessionID = r.SessionID + + // Store the ResumeGatewayURL within the Session struct. + s.resumeGatewayURL = r.ResumeGatewayURL } diff --git a/events.go b/events.go index ccc7346..0ff2cb4 100644 --- a/events.go +++ b/events.go @@ -36,13 +36,14 @@ type Event struct { // A Ready stores all data for the websocket READY event. type Ready struct { - Version int `json:"v"` - SessionID string `json:"session_id"` - User *User `json:"user"` - Shard *[2]int `json:"shard"` - Application *Application `json:"application"` - Guilds []*Guild `json:"guilds"` - PrivateChannels []*Channel `json:"private_channels"` + Version int `json:"v"` + SessionID string `json:"session_id"` + User *User `json:"user"` + Shard *[2]int `json:"shard"` + ResumeGatewayURL string `json:"resume_gateway_url"` + Application *Application `json:"application"` + Guilds []*Guild `json:"guilds"` + PrivateChannels []*Channel `json:"private_channels"` } // ChannelCreate is the data for a ChannelCreate event. diff --git a/structs.go b/structs.go index 24c10c2..920581a 100644 --- a/structs.go +++ b/structs.go @@ -126,6 +126,9 @@ type Session struct { // sequence tracks the current gateway api websocket sequence number sequence *int64 + // stores sessions current Discord Resume Gateway + resumeGatewayURL string + // stores sessions current Discord Gateway gateway string diff --git a/wsapi.go b/wsapi.go index d101c54..5b94cb6 100644 --- a/wsapi.go +++ b/wsapi.go @@ -62,22 +62,32 @@ func (s *Session) Open() error { return ErrWSAlreadyOpen } + sequence := atomic.LoadInt64(s.sequence) + + var gateway string // Get the gateway to use for the Websocket connection - if s.gateway == "" { - s.gateway, err = s.Gateway() - if err != nil { - return err + if sequence != 0 && s.sessionID != "" && s.resumeGatewayURL != "" { + s.log(LogDebug, "using resume gateway %s", s.resumeGatewayURL) + gateway = s.resumeGatewayURL + } else { + if s.gateway == "" { + s.gateway, err = s.Gateway() + if err != nil { + return err + } } - // Add the version and encoding to the URL - s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" + gateway = s.gateway } + // Add the version and encoding to the URL + gateway += "?v=" + APIVersion + "&encoding=json" + // Connect to the Gateway - s.log(LogInformational, "connecting to gateway %s", s.gateway) + s.log(LogInformational, "connecting to gateway %s", gateway) header := http.Header{} header.Add("accept-encoding", "zlib") - s.wsConn, _, err = s.Dialer.Dial(s.gateway, header) + s.wsConn, _, err = s.Dialer.Dial(gateway, header) if err != nil { s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err) s.gateway = "" // clear cached gateway @@ -123,7 +133,6 @@ func (s *Session) Open() error { // Now we send either an Op 2 Identity if this is a brand new // connection or Op 6 Resume if we are resuming an existing connection. - sequence := atomic.LoadInt64(s.sequence) if s.sessionID == "" && sequence == 0 { // Send Op 2 Identity Packet @@ -616,15 +625,23 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { // Invalid Session // Must respond with a Identify packet. if e.Operation == 9 { + s.log(LogInformational, "Closing and reconnecting in response to Op9") + s.CloseWithCode(websocket.CloseServiceRestart) - s.log(LogInformational, "sending identify packet to gateway in response to Op9") - - err = s.identify() - if err != nil { - s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) + var resumable bool + if err := json.Unmarshal(e.RawData, &resumable); err != nil { + s.log(LogError, "error unmarshalling invalid session event, %s", err) return e, err } + if !resumable { + s.log(LogInformational, "Gateway session is not resumable, discarding its information") + s.resumeGatewayURL = "" + s.sessionID = "" + atomic.StoreInt64(s.sequence, 0) + } + + s.reconnect() return e, nil }