ScuttleBot

fix: HTTP transport since cursor — avoid full history poll on each interval (#28) Server: handleChannelMessages accepts ?since=<RFC3339Nano> and filters messages server-side, so only new messages are returned. Client: MessagesSince appends ?since= to each channel fetch when since is non-zero; removes redundant client-side at.After(since) filter since the server handles it.

lmata 2026-04-01 20:28 trunk
Commit b71f8ab98c45ddb1e9fb2adacfc1429292f6ad8904e3e6c9359262b1769213fd
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -44,10 +44,23 @@
4444
// Auto-join on first access so the bridge starts tracking this channel.
4545
s.bridge.JoinChannel(channel)
4646
msgs := s.bridge.Messages(channel)
4747
if msgs == nil {
4848
msgs = []bridge.Message{}
49
+ }
50
+ // Filter by ?since=<RFC3339> when provided (avoids sending full history on each poll).
51
+ if sinceStr := r.URL.Query().Get("since"); sinceStr != "" {
52
+ since, err := time.Parse(time.RFC3339Nano, sinceStr)
53
+ if err == nil {
54
+ filtered := msgs[:0]
55
+ for _, m := range msgs {
56
+ if m.At.After(since) {
57
+ filtered = append(filtered, m)
58
+ }
59
+ }
60
+ msgs = filtered
61
+ }
4962
}
5063
writeJSON(w, http.StatusOK, map[string]any{"messages": msgs})
5164
}
5265
5366
func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) {
5467
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -44,10 +44,23 @@
44 // Auto-join on first access so the bridge starts tracking this channel.
45 s.bridge.JoinChannel(channel)
46 msgs := s.bridge.Messages(channel)
47 if msgs == nil {
48 msgs = []bridge.Message{}
 
 
 
 
 
 
 
 
 
 
 
 
 
49 }
50 writeJSON(w, http.StatusOK, map[string]any{"messages": msgs})
51 }
52
53 func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) {
54
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -44,10 +44,23 @@
44 // Auto-join on first access so the bridge starts tracking this channel.
45 s.bridge.JoinChannel(channel)
46 msgs := s.bridge.Messages(channel)
47 if msgs == nil {
48 msgs = []bridge.Message{}
49 }
50 // Filter by ?since=<RFC3339> when provided (avoids sending full history on each poll).
51 if sinceStr := r.URL.Query().Get("since"); sinceStr != "" {
52 since, err := time.Parse(time.RFC3339Nano, sinceStr)
53 if err == nil {
54 filtered := msgs[:0]
55 for _, m := range msgs {
56 if m.At.After(since) {
57 filtered = append(filtered, m)
58 }
59 }
60 msgs = filtered
61 }
62 }
63 writeJSON(w, http.StatusOK, map[string]any{"messages": msgs})
64 }
65
66 func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) {
67
--- pkg/sessionrelay/http.go
+++ pkg/sessionrelay/http.go
@@ -72,11 +72,15 @@
7272
}
7373
7474
func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) {
7575
out := make([]Message, 0, 32)
7676
for _, channel := range c.Channels() {
77
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/v1/channels/"+channelSlug(channel)+"/messages", nil)
77
+ url := c.baseURL + "/v1/channels/" + channelSlug(channel) + "/messages"
78
+ if !since.IsZero() {
79
+ url += "?since=" + since.UTC().Format(time.RFC3339Nano)
80
+ }
81
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
7882
if err != nil {
7983
return nil, err
8084
}
8185
c.authorize(req)
8286
@@ -101,13 +105,10 @@
101105
for _, msg := range payload.Messages {
102106
at, err := time.Parse(time.RFC3339Nano, msg.At)
103107
if err != nil {
104108
continue
105109
}
106
- if !at.After(since) {
107
- continue
108
- }
109110
out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text})
110111
}
111112
}
112113
sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) })
113114
return out, nil
114115
--- pkg/sessionrelay/http.go
+++ pkg/sessionrelay/http.go
@@ -72,11 +72,15 @@
72 }
73
74 func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) {
75 out := make([]Message, 0, 32)
76 for _, channel := range c.Channels() {
77 req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/v1/channels/"+channelSlug(channel)+"/messages", nil)
 
 
 
 
78 if err != nil {
79 return nil, err
80 }
81 c.authorize(req)
82
@@ -101,13 +105,10 @@
101 for _, msg := range payload.Messages {
102 at, err := time.Parse(time.RFC3339Nano, msg.At)
103 if err != nil {
104 continue
105 }
106 if !at.After(since) {
107 continue
108 }
109 out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text})
110 }
111 }
112 sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) })
113 return out, nil
114
--- pkg/sessionrelay/http.go
+++ pkg/sessionrelay/http.go
@@ -72,11 +72,15 @@
72 }
73
74 func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) {
75 out := make([]Message, 0, 32)
76 for _, channel := range c.Channels() {
77 url := c.baseURL + "/v1/channels/" + channelSlug(channel) + "/messages"
78 if !since.IsZero() {
79 url += "?since=" + since.UTC().Format(time.RFC3339Nano)
80 }
81 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
82 if err != nil {
83 return nil, err
84 }
85 c.authorize(req)
86
@@ -101,13 +105,10 @@
105 for _, msg := range payload.Messages {
106 at, err := time.Parse(time.RFC3339Nano, msg.At)
107 if err != nil {
108 continue
109 }
 
 
 
110 out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text})
111 }
112 }
113 sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) })
114 return out, nil
115

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button