feat: add cli contract and local browser events
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"chub/internal/domain"
|
||||
)
|
||||
|
||||
// EventBus is the in-process local event channel shared by the UI and CLI
|
||||
// adapters. Subscribers own cancellation; publishing never blocks a browser
|
||||
// process monitor on a slow presentation consumer.
|
||||
type EventBus struct {
|
||||
mu sync.RWMutex
|
||||
nextID uint64
|
||||
clients map[uint64]chan domain.BrowserEvent
|
||||
}
|
||||
|
||||
func NewEventBus() *EventBus { return &EventBus{clients: make(map[uint64]chan domain.BrowserEvent)} }
|
||||
|
||||
func (b *EventBus) Subscribe(buffer int) (<-chan domain.BrowserEvent, func()) {
|
||||
if buffer < 1 {
|
||||
buffer = 1
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.nextID++
|
||||
id := b.nextID
|
||||
ch := make(chan domain.BrowserEvent, buffer)
|
||||
b.clients[id] = ch
|
||||
b.mu.Unlock()
|
||||
return ch, func() {
|
||||
b.mu.Lock()
|
||||
if existing, ok := b.clients[id]; ok {
|
||||
delete(b.clients, id)
|
||||
close(existing)
|
||||
}
|
||||
b.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *EventBus) Publish(event domain.BrowserEvent) {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
for _, ch := range b.clients {
|
||||
select {
|
||||
case ch <- event:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chub/internal/domain"
|
||||
)
|
||||
|
||||
func TestEventBusPublishesAndCancels(t *testing.T) {
|
||||
bus := NewEventBus()
|
||||
ch, cancel := bus.Subscribe(1)
|
||||
event := domain.BrowserEvent{Kind: domain.EventBrowserStarted, InstanceID: "demo"}
|
||||
bus.Publish(event)
|
||||
select {
|
||||
case got := <-ch:
|
||||
if got.InstanceID != event.InstanceID || got.Kind != event.Kind {
|
||||
t.Fatalf("unexpected event: %#v", got)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("event was not published")
|
||||
}
|
||||
cancel()
|
||||
bus.Publish(event)
|
||||
select {
|
||||
case _, ok := <-ch:
|
||||
if ok {
|
||||
t.Fatal("cancelled subscriber received an event")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("subscriber was not closed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user