feat(eventsystem): plumbing for plugin<->plugin comms

The only thing left is to add an API endpoint for broadcasting
EventCustom events (other event types should not be emittible by
plugins, the use-case isn't there since plugins can already talk to
Zoraxy via the API).
Input to the endput should be a json-encoded `CustomEvent`
This commit is contained in:
Anthony Rubick
2025-09-07 18:03:48 -05:00
parent 2f98ecd0c6
commit c57fa39554
10 changed files with 310 additions and 41 deletions

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }

View File

@@ -86,8 +86,39 @@ func (em *eventManager) UnregisterSubscriber(listenerID ListenerID) error {
return nil return nil
} }
// EmitToSubscribersAnd dispatches an event to the specific listeners in addition to the events subscribers.
//
// The primary use-case of this function is for plugin-to-plugin communication
func (em *eventManager) EmitToSubscribersAnd(listenerIDs []ListenerID, payload events.EventPayload) {
eventName := payload.GetName()
if len(listenerIDs) == 0 {
return // No subscribers
}
// Create the event
event := events.Event{
Name: eventName,
Timestamp: time.Now().Unix(),
UUID: uuid.New().String(),
Data: payload,
}
// Dispatch to all specified listeners asynchronously
em.emitTo(listenerIDs, event)
// Also emit to all subscribers of the event as usual
em.mutex.RLock()
subscribers, exists := em.subscriptions[eventName]
em.mutex.RUnlock()
if !exists || len(subscribers) == 0 {
return // No subscribers
}
em.emitTo(subscribers, event)
}
// Emit dispatches an event to all subscribed listeners // Emit dispatches an event to all subscribed listeners
func (em *eventManager) Emit(payload events.EventPayload) error { func (em *eventManager) Emit(payload events.EventPayload) {
eventName := payload.GetName() eventName := payload.GetName()
em.mutex.RLock() em.mutex.RLock()
@@ -95,7 +126,7 @@ func (em *eventManager) Emit(payload events.EventPayload) error {
subscribers, exists := em.subscriptions[eventName] subscribers, exists := em.subscriptions[eventName]
if !exists || len(subscribers) == 0 { if !exists || len(subscribers) == 0 {
return nil // No subscribers return // No subscribers
} }
// Create the event // Create the event
@@ -107,11 +138,26 @@ func (em *eventManager) Emit(payload events.EventPayload) error {
} }
// Dispatch to all subscribers asynchronously // Dispatch to all subscribers asynchronously
for _, listenerID := range subscribers { em.emitTo(subscribers, event)
}
// Dispatch event to all specified listeners asynchronously
func (em *eventManager) emitTo(listenerIDs []ListenerID, event events.Event) {
if len(listenerIDs) == 0 {
return
}
// Dispatch to all specified listeners asynchronously
em.mutex.RLock()
defer em.mutex.RUnlock()
for _, listenerID := range listenerIDs {
listener, exists := em.subscribers[listenerID] listener, exists := em.subscribers[listenerID]
if !exists { if !exists {
em.logger.PrintAndLog("event-system", "Failed to get listener for event dispatch, removing "+string(listenerID)+" from subscriptions", nil) em.logger.PrintAndLog("event-system", "Failed to get listener for event dispatch, removing "+string(listenerID)+" from subscriptions", nil)
// Remove the listener from the subscription list
// This is done in a separate goroutine to avoid deadlock
go em.UnregisterSubscriber(listenerID)
continue continue
} }
@@ -121,6 +167,4 @@ func (em *eventManager) Emit(payload events.EventPayload) error {
} }
}(listener) }(listener)
} }
return nil
} }

View File

@@ -20,10 +20,8 @@ type EventPayload interface {
// Event represents a system event // Event represents a system event
type Event struct { type Event struct {
Name EventName `json:"name"` Name EventName `json:"name"`
// Unix timestamp Timestamp int64 `json:"timestamp"` // Unix timestamp
Timestamp int64 `json:"timestamp"` UUID string `json:"uuid"` // UUID for the event
// UUID for the event
UUID string `json:"uuid"`
Data EventPayload `json:"data"` Data EventPayload `json:"data"`
} }
@@ -34,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled" EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created // EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated" EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed // Add more event types as needed
) )
@@ -42,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true, EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true, EventBlacklistToggled: true,
EventAccessRuleCreated: true, EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed // Add more event types as needed
// NOTE: Keep up-to-date with event names specified above // NOTE: Keep up-to-date with event names specified above
} }
@@ -100,6 +102,20 @@ func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api" return "accesslist-api"
} }
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct // ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error { func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data // First, determine the event type, and parse shared fields, from the JSON data
@@ -146,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err return err
} }
event.Data = &payload.Data event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default: default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
} }