- Add internal event bus (server/internal/events/) with synchronous pub/sub and panic isolation per listener - Upgrade WebSocket Hub to workspace-scoped rooms with JWT auth and membership verification on connect - Add 10 new WS event types (comment CRUD, inbox read/archive, agent create/delete, workspace/member events) - Refactor all handlers and TaskService to publish events via Bus instead of direct Hub.Broadcast calls - Add WS broadcast listener that routes events to correct workspace - Frontend: WSClient sends token + workspace_id on connect with auto-reconnect refetch - Frontend: centralized useRealtimeSync hook dispatches all WS events to global Zustand stores - Migrate issues and inbox pages from local useState to global useIssueStore/useInboxStore - Make store addIssue/addItem idempotent to prevent duplicates - Remove dead packages/hooks/src/use-realtime.ts - Add feature tracking files for 4 planned features Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
59 lines
1.5 KiB
Go
59 lines
1.5 KiB
Go
package events
|
|
|
|
import (
|
|
"log"
|
|
"sync"
|
|
)
|
|
|
|
// Event represents a domain event published by handlers or services.
|
|
type Event struct {
|
|
Type string // e.g. "issue:created", "inbox:new"
|
|
WorkspaceID string // routes to correct Hub room
|
|
ActorType string // "member", "agent", or "system"
|
|
ActorID string
|
|
Payload any // JSON-serializable, same shape as current WS payloads
|
|
}
|
|
|
|
// Handler is a function that processes an event.
|
|
type Handler func(Event)
|
|
|
|
// Bus is an in-process synchronous pub/sub event bus.
|
|
type Bus struct {
|
|
mu sync.RWMutex
|
|
listeners map[string][]Handler
|
|
}
|
|
|
|
// New creates a new event bus.
|
|
func New() *Bus {
|
|
return &Bus{
|
|
listeners: make(map[string][]Handler),
|
|
}
|
|
}
|
|
|
|
// Subscribe registers a handler for a given event type.
|
|
// Handlers are called synchronously in registration order.
|
|
func (b *Bus) Subscribe(eventType string, h Handler) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.listeners[eventType] = append(b.listeners[eventType], h)
|
|
}
|
|
|
|
// Publish dispatches an event to all registered handlers for that event type.
|
|
// Each handler is called synchronously. Panics in individual handlers are
|
|
// recovered so one failing handler does not prevent others from executing.
|
|
func (b *Bus) Publish(e Event) {
|
|
b.mu.RLock()
|
|
handlers := b.listeners[e.Type]
|
|
b.mu.RUnlock()
|
|
|
|
for _, h := range handlers {
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Printf("[event-bus] panic in listener for %q: %v", e.Type, r)
|
|
}
|
|
}()
|
|
h(e)
|
|
}()
|
|
}
|
|
}
|