fix: CLI poll loop used UUID instead of queue cursor (last_id)
The poll loop was storing msg.ID (UUID string) as afterID, but the server expects the integer queue cursor from last_id. This caused the CLI to re-fetch ALL messages on every poll cycle. - Change PollMessages to accept int64 afterID and return PollResult with LastID - Track lastQID (queue cursor) instead of lastMsgID (UUID) - Parse the wrapped MessagesResponse properly
This commit is contained in:
@@ -97,21 +97,18 @@ func (c *Client) SendMessage(msg *Message) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PollMessages long-polls for new messages.
|
// PollMessages long-polls for new messages. afterID is the queue cursor (last_id).
|
||||||
func (c *Client) PollMessages(afterID string, timeout int) ([]Message, error) {
|
func (c *Client) PollMessages(afterID int64, timeout int) (*PollResult, error) {
|
||||||
// Use a longer HTTP timeout than the server long-poll timeout.
|
// Use a longer HTTP timeout than the server long-poll timeout.
|
||||||
client := &http.Client{Timeout: time.Duration(timeout+5) * time.Second}
|
client := &http.Client{Timeout: time.Duration(timeout+5) * time.Second}
|
||||||
|
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
if afterID != "" {
|
if afterID > 0 {
|
||||||
params.Set("after", afterID)
|
params.Set("after", fmt.Sprintf("%d", afterID))
|
||||||
}
|
}
|
||||||
params.Set("timeout", fmt.Sprintf("%d", timeout))
|
params.Set("timeout", fmt.Sprintf("%d", timeout))
|
||||||
|
|
||||||
path := "/api/v1/messages"
|
path := "/api/v1/messages?" + params.Encode()
|
||||||
if len(params) > 0 {
|
|
||||||
path += "?" + params.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
|
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,18 +131,15 @@ func (c *Client) PollMessages(afterID string, timeout int) ([]Message, error) {
|
|||||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(data))
|
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The server may return an array directly or wrapped.
|
|
||||||
var msgs []Message
|
|
||||||
if err := json.Unmarshal(data, &msgs); err != nil {
|
|
||||||
// Try wrapped format.
|
|
||||||
var wrapped MessagesResponse
|
var wrapped MessagesResponse
|
||||||
if err2 := json.Unmarshal(data, &wrapped); err2 != nil {
|
if err := json.Unmarshal(data, &wrapped); err != nil {
|
||||||
return nil, fmt.Errorf("decode messages: %w (raw: %s)", err, string(data))
|
return nil, fmt.Errorf("decode messages: %w (raw: %s)", err, string(data))
|
||||||
}
|
}
|
||||||
msgs = wrapped.Messages
|
|
||||||
}
|
|
||||||
|
|
||||||
return msgs, nil
|
return &PollResult{
|
||||||
|
Messages: wrapped.Messages,
|
||||||
|
LastID: wrapped.LastID,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinChannel joins a channel via the unified command endpoint.
|
// JoinChannel joins a channel via the unified command endpoint.
|
||||||
|
|||||||
@@ -71,6 +71,13 @@ type ServerInfo struct {
|
|||||||
// MessagesResponse wraps polling results.
|
// MessagesResponse wraps polling results.
|
||||||
type MessagesResponse struct {
|
type MessagesResponse struct {
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
|
LastID int64 `json:"last_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollResult wraps the poll response including the cursor.
|
||||||
|
type PollResult struct {
|
||||||
|
Messages []Message
|
||||||
|
LastID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTS parses the message timestamp.
|
// ParseTS parses the message timestamp.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type App struct {
|
|||||||
nick string
|
nick string
|
||||||
target string // current target (#channel or nick for DM)
|
target string // current target (#channel or nick for DM)
|
||||||
connected bool
|
connected bool
|
||||||
lastMsgID string
|
lastQID int64 // queue cursor for polling
|
||||||
stopPoll chan struct{}
|
stopPoll chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ func (a *App) cmdConnect(serverURL string) {
|
|||||||
a.client = client
|
a.client = client
|
||||||
a.nick = resp.Nick
|
a.nick = resp.Nick
|
||||||
a.connected = true
|
a.connected = true
|
||||||
a.lastMsgID = ""
|
a.lastQID = 0
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
a.ui.AddStatus(fmt.Sprintf("[green]Connected! Nick: %s, Session: %s", resp.Nick, resp.SessionID))
|
a.ui.AddStatus(fmt.Sprintf("[green]Connected! Nick: %s, Session: %s", resp.Nick, resp.SessionID))
|
||||||
@@ -462,27 +462,28 @@ func (a *App) pollLoop() {
|
|||||||
|
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
client := a.client
|
client := a.client
|
||||||
lastID := a.lastMsgID
|
lastQID := a.lastQID
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs, err := client.PollMessages(lastID, 15)
|
result, err := client.PollMessages(lastQID, 15)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Transient error — retry after delay.
|
// Transient error — retry after delay.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range msgs {
|
if result.LastID > 0 {
|
||||||
a.handleServerMessage(&msg)
|
|
||||||
if msg.ID != "" {
|
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
a.lastMsgID = msg.ID
|
a.lastQID = result.LastID
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, msg := range result.Messages {
|
||||||
|
a.handleServerMessage(&msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user