mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix(lint): delete dead ws broadcast cluster; demote phase-header comments
golangci-lint had been failing invisibly behind the earlier CI gate failures. Default-build lint is now clean: - Delete the unused pre-topic-limiter rate-limit constants, the unused bluemonday sanitizer, and the dead broadcast variants superseded by their Low/High counterparts (broadcastExclude, broadcastToDMParticipants(+Exclude), sendSequencedToUsers, PubSub.debugDump). Test references were comments only; updated to name the live variants. - Separate 'Phase X Step Y' file headers from the package clause with a blank line so staticcheck ST1000 no longer reads them as malformed package comments (proper package docs exist in hub.go/manifest.go). - Add .gitattributes normalizing line endings to LF on checkout — the Windows CI runner materialized CRLF, which made every prettier-formatted file fail the format gate. Known remainder (pre-existing, out of P0 scope): golangci-lint with -tags wazero reports 3 gosec + 2 staticcheck and -tags otel 1+1; CI lints the default build. Tracked for the P1 plugin pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// Plugins get a per-plugin namespaced KV store backed by the PluginStore
|
||||
// rows in the events/plugin schema. Capacity caps and value-size caps are
|
||||
// enforced here so a misbehaving plugin can't fill the database.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// A plugin that declares the `ui` capability ships HTML/CSS/JS assets and a
|
||||
// list of tabs. The host serves those assets at /api/v1/plugins/<name>/ui/...
|
||||
// and the Solid.js client bridge renders each tab inside a sandboxed iframe.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
//
|
||||
// Loader walks the directory, parses every plugin.json, and returns a slice
|
||||
// of foundPlugin records. The Registry then persists each into the store.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//go:build !wazero
|
||||
|
||||
// Default build stub — TOML manifest parsing is not compiled in without -tags wazero.
|
||||
|
||||
package plugin
|
||||
|
||||
// tryLoadPluginTOML always reports "not present" in the default build so the
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// directory and persists each manifest into the PluginStore so admins can see
|
||||
// what is "installed", but the .wasm files are NOT executed. Calling
|
||||
// Dispatch() in the default build returns ErrRuntimeUnavailable.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Default plugin runtime: no Wazero. Plugin manifests are still discovered,
|
||||
// persisted, and surfaced through the admin API, but `.wasm` modules are not
|
||||
// executed. To enable real WASM execution build with `-tags wazero`.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -294,7 +294,7 @@ func TestDM_ChatEdit_ParticipantCanEdit(t *testing.T) {
|
||||
hub.HandleMessageForTest(cAlice, dmChatEditMsg(msgID, "edited"))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Alice should receive the chat_edited broadcast (via broadcastToDMParticipants).
|
||||
// Alice should receive the chat_edited broadcast (via the sequenced DM event path).
|
||||
msgs := dmDrainAll(sendAlice)
|
||||
edited := dmFindMsgType(msgs, "chat_edited")
|
||||
if edited == nil {
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestEmitEvents_ExcludeSenderEvent(t *testing.T) {
|
||||
|
||||
h.EmitEvents(events)
|
||||
|
||||
// broadcastExclude is synchronous — check immediately.
|
||||
// broadcastExcludeLow is synchronous — check immediately.
|
||||
senderMsgs := drainChan(sendSender, 50*time.Millisecond)
|
||||
otherMsgs := drainChan(sendOther, 50*time.Millisecond)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// StartEventPruner runs a background goroutine that deletes events older than
|
||||
// the configured retention window. It is the bounded-storage half of the
|
||||
// event persistence design: the persister appends, the pruner trims.
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
|
||||
+5
-62
@@ -5,30 +5,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/owncord/server/auth"
|
||||
"github.com/owncord/server/db"
|
||||
)
|
||||
|
||||
// Rate limit windows.
|
||||
const (
|
||||
chatRateLimit = 10
|
||||
chatWindow = time.Second
|
||||
typingRateLimit = 1
|
||||
typingWindow = 3 * time.Second
|
||||
presenceRateLimit = 1
|
||||
presenceWindow = 10 * time.Second
|
||||
reactionRateLimit = 5
|
||||
reactionWindow = time.Second
|
||||
)
|
||||
|
||||
// maxMessageLen is the maximum allowed message length in runes (Unicode code points).
|
||||
const maxMessageLen = 4000
|
||||
|
||||
var sanitizer = bluemonday.StrictPolicy()
|
||||
|
||||
// HandleMessageForTest dispatches a raw WebSocket message from client c.
|
||||
// Exported so ws_test package can invoke it directly without a real connection.
|
||||
func (h *Hub) HandleMessageForTest(c *Client, raw []byte) {
|
||||
@@ -226,21 +207,11 @@ func (h *Hub) requireChannelPerm(c *Client, channelID int64, perm int64, permLab
|
||||
return false
|
||||
}
|
||||
|
||||
// broadcastExclude sends a message to all clients in the sender's channel
|
||||
// EXCEPT the sender. Unlike hub.BroadcastToChannel, messages sent via this
|
||||
// function are NOT stored in the replay ring buffer — they are ephemeral.
|
||||
// This is correct for typing indicators but would be incorrect for messages
|
||||
// that should survive reconnection replay.
|
||||
func (h *Hub) broadcastExclude(channelID, excludeUserID int64, msg []byte) {
|
||||
if channelID == 0 {
|
||||
h.pubsub.Publish(TopicGlobal, msg, excludeUserID)
|
||||
return
|
||||
}
|
||||
h.pubsub.Publish(ChannelTopic(channelID), msg, excludeUserID)
|
||||
}
|
||||
|
||||
// broadcastExcludeLow is like broadcastExclude but at low priority.
|
||||
// Used for typing indicators — dropped on overflow instead of disconnecting.
|
||||
// broadcastExcludeLow sends a message at low priority to all clients in the
|
||||
// sender's channel EXCEPT the sender. Messages sent via this function are NOT
|
||||
// stored in the replay ring buffer — they are ephemeral. This is correct for
|
||||
// typing indicators (dropped on overflow instead of disconnecting) but would
|
||||
// be incorrect for messages that should survive reconnection replay.
|
||||
func (h *Hub) broadcastExcludeLow(channelID, excludeUserID int64, msg []byte) {
|
||||
if channelID == 0 {
|
||||
h.pubsub.PublishLow(TopicGlobal, msg, excludeUserID)
|
||||
@@ -249,31 +220,3 @@ func (h *Hub) broadcastExcludeLow(channelID, excludeUserID int64, msg []byte) {
|
||||
h.pubsub.PublishLow(ChannelTopic(channelID), msg, excludeUserID)
|
||||
}
|
||||
|
||||
// broadcastToDMParticipants sends a message to all participants of a DM channel
|
||||
// while preserving DM semantics (delivery is by participant, not channel focus).
|
||||
// Unlike broadcastToDMParticipantsExclude, this path is sequenced and replayable.
|
||||
func (h *Hub) broadcastToDMParticipants(channelID int64, msg []byte) {
|
||||
participantIDs, err := h.db.GetDMParticipantIDs(channelID)
|
||||
if err != nil {
|
||||
slog.Error("broadcastToDMParticipants GetDMParticipantIDs", "err", err, "channel_id", channelID)
|
||||
return
|
||||
}
|
||||
h.sendSequencedToUsers(channelID, participantIDs, msg)
|
||||
}
|
||||
|
||||
// broadcastToDMParticipantsExclude sends a message to all participants of a DM
|
||||
// channel EXCEPT the specified user. Used for ephemeral events like typing
|
||||
// indicators where echoing back to the sender is undesirable.
|
||||
func (h *Hub) broadcastToDMParticipantsExclude(channelID, excludeUserID int64, msg []byte) {
|
||||
participantIDs, err := h.db.GetDMParticipantIDs(channelID)
|
||||
if err != nil {
|
||||
slog.Error("broadcastToDMParticipantsExclude GetDMParticipantIDs", "err", err, "channel_id", channelID)
|
||||
return
|
||||
}
|
||||
for _, pid := range participantIDs {
|
||||
if pid == excludeUserID {
|
||||
continue
|
||||
}
|
||||
h.SendToUser(pid, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// plugin returns a Reply, it is sent only to the invoking client (ephemeral).
|
||||
// If the plugin returns a Broadcast string, it is broadcast to the channel
|
||||
// only after verifying the invoking client holds SEND_MESSAGES permission.
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
|
||||
@@ -1695,7 +1695,7 @@ func TestTyping_RateLimited_SilentlyDropped(t *testing.T) {
|
||||
|
||||
// TestBroadcastExclude_SendsToOthersNotSelf verifies that broadcastExclude
|
||||
// delivers to all channel members except the excluded user.
|
||||
// This is exercised indirectly via typing_start (which calls broadcastExclude).
|
||||
// This is exercised indirectly via typing_start (which calls broadcastExcludeLow).
|
||||
func TestBroadcastExclude_SendsToOthersNotSelf(t *testing.T) {
|
||||
hub, database := newHandlerHub(t)
|
||||
chID := seedTestChannel(t, database, "excl-chan1")
|
||||
|
||||
+3
-20
@@ -555,26 +555,9 @@ func (h *Hub) BroadcastToAllLow(msg []byte) {
|
||||
h.pubsub.PublishGlobalLow(msg)
|
||||
}
|
||||
|
||||
// sendSequencedToUsers stamps msg with a monotonic seq, stores it in the replay
|
||||
// buffer under channelID, and fanouts the wrapped payload to the provided users.
|
||||
func (h *Hub) sendSequencedToUsers(channelID int64, userIDs []int64, msg []byte) {
|
||||
h.seqMu.Lock()
|
||||
defer h.seqMu.Unlock()
|
||||
|
||||
seq := h.nextSeq()
|
||||
wrapped := wrapWithSeq(msg, seq)
|
||||
|
||||
// Store DM event for reconnect replay; filtering is channel-based and uses
|
||||
// allowed channel IDs computed at auth time (including open DMs).
|
||||
h.replayBuf.Push(seq, channelID, wrapped)
|
||||
h.persistEvent(seq, channelID, wrapped)
|
||||
|
||||
for _, userID := range userIDs {
|
||||
h.SendToUser(userID, wrapped)
|
||||
}
|
||||
}
|
||||
|
||||
// sendSequencedToUsersHigh is like sendSequencedToUsers but uses high-priority delivery.
|
||||
// sendSequencedToUsersHigh stamps msg with a monotonic seq, stores it in the
|
||||
// replay buffer under channelID, and fans the wrapped payload out to the
|
||||
// provided users with high-priority delivery.
|
||||
func (h *Hub) sendSequencedToUsersHigh(channelID int64, userIDs []int64, msg []byte) {
|
||||
h.seqMu.Lock()
|
||||
defer h.seqMu.Unlock()
|
||||
|
||||
@@ -2,7 +2,6 @@ package ws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -217,16 +216,3 @@ func (ps *PubSub) TopicsForClient(userID int64) []Topic {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// debugDump logs the current subscription state. For development use only.
|
||||
func (ps *PubSub) debugDump() {
|
||||
ps.mu.RLock()
|
||||
defer ps.mu.RUnlock()
|
||||
for topic, subs := range ps.topics {
|
||||
ids := make([]int64, 0, len(subs))
|
||||
for uid := range subs {
|
||||
ids = append(ids, uid)
|
||||
}
|
||||
slog.Debug("pubsub: topic", "topic", string(topic), "subscribers", ids)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user