Keep the gateway connection alive and resume hosted queues after reconnecting
CI / build (push) Successful in 30s

The client doesn't ping while it's sending, so when the proxy answers its frames itself (everything it
sends in a hosted room), IMVU's gateway saw an idle connection and closed it after about a minute. A
ping now goes to the gateway in place of each answered frame. And after a reconnect, hosted rooms'
queues are taken out of C2gOpenFloodgates and answered here (the gateway never answered them, so the
client gave up and closed the room).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 05:37:53 -07:00
1 parent c483099558
commit 08a2ed94a3
4 files changed
+94 -12

No files matched your search

+28 -2
View File
@@ -253,12 +253,33 @@ func (st *stream) filtered(f imq.Frame, frame []byte) []byte {
}
rest := router.Route(st.conn, f.Type == imq.C2gUnsubscribe, sub)
if rest.Empty() {
return nil
return keepalive()
}
if len(rest.All()) == len(sub.All()) {
return frame
}
return imq.EncodeFrame(f.Type, rest.Marshal())
case imq.C2gOpenFloodgates:
// Resuming queues after a reconnect: the router's are answered here, the rest go to the gateway.
router, ok := st.watcher.(IMQRouter)
if !ok {
return frame
}
states, err := imq.ParseFloodgates(f.Data)
if err != nil {
return frame
}
var rest []imq.QueueOp
for _, q := range states {
if router.Route(st.conn, false, imq.Subscription{WithResults: []imq.QueueOp{q}}).Empty() {
continue
}
rest = append(rest, q)
}
if len(rest) == len(states) {
return frame
}
return imq.EncodeFrame(imq.C2gOpenFloodgates, imq.MarshalFloodgates(rest))
case imq.G2cSendMessage:
m, err := imq.ParseDelivered(f.Data)
if err != nil {
@@ -284,7 +305,7 @@ func (st *stream) filtered(f imq.Frame, frame []byte) []byte {
if st.reply != nil {
st.reply.Write(imq.EncodeFrame(imq.G2cResult, imq.Result(m.OpID, 0)))
}
return nil
return keepalive()
}
if bytes.Equal(out, m.Message) {
return frame
@@ -293,6 +314,11 @@ func (st *stream) filtered(f imq.Frame, frame []byte) []byte {
return imq.EncodeFrame(imq.C2gSendMessage, m.Marshal())
}
// keepalive is what goes to the gateway in place of a frame the proxy answered itself: a ping. The client doesn't
// ping while it's sending, so without it the gateway sees an idle connection and closes it (in a hosted room, where
// everything the client sends is answered here).
func keepalive() []byte { return imq.EncodeFrame(imq.C2gPing, nil) }
func (st *stream) summary() string {
if st.other {
return fmt.Sprintf("%s %dB", st.name, st.bytes)