feat: add function to assist parsing events

This commit is contained in:
Anthony Rubick
2025-07-20 17:43:52 -07:00
parent 9c99f6c734
commit d6c907b13f
5 changed files with 151 additions and 10 deletions

View File

@@ -109,7 +109,7 @@ func (em *eventManager) Emit(payload zoraxyPlugin.EventPayload) error {
// Create the event
event := zoraxyPlugin.Event{
Name: eventName,
Timestamp: time.Now(),
Timestamp: time.Now().Unix(),
Data: payload,
}
@@ -176,6 +176,17 @@ func (em *eventManager) dispatchToPlugin(pluginID string, event zoraxyPlugin.Eve
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
em.logger.PrintAndLog("event-system", "Plugin "+pluginID+" returned non-200 status for event: "+resp.Status, nil)
respBody := fmt.Errorf("no response body")
if resp.ContentLength > 0 {
buffer := bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
_, respErr := buffer.ReadFrom(resp.Body)
if respErr != nil {
respBody = fmt.Errorf("failed to read response body: %v", respErr)
} else {
respBody = fmt.Errorf("response body: %s", buffer.String())
}
}
em.logger.PrintAndLog("event-system", fmt.Sprintf("Plugin %s returned non-200 status for event `%s`: %s", pluginID, event.Name, resp.Status), respBody)
}
}

View File

@@ -0,0 +1,88 @@
package plugins
import (
"encoding/json"
"fmt"
"testing"
"time"
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin"
)
// Test (de)serialization of events
func TestEventDeSerialization(t *testing.T) {
type SerializationTest struct {
name string
event zoraxy_plugin.Event
expectedJson string
}
timestamp := time.Now().Unix()
tests := []SerializationTest{
{
name: "BlacklistedIPBlocked",
event: zoraxy_plugin.Event{
Name: zoraxy_plugin.EventBlacklistedIPBlocked,
Timestamp: timestamp,
Data: &zoraxy_plugin.BlacklistedIPBlockedEvent{
IP: "192.168.1.1",
Comment: "Test comment",
RequestedURL: "http://example.com",
Hostname: "example.com",
UserAgent: "TestUserAgent",
Method: "GET",
},
},
expectedJson: `{"name":"blacklistedIpBlocked","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"ip":"192.168.1.1","comment":"Test comment","requested_url":"http://example.com","hostname":"example.com","user_agent":"TestUserAgent","method":"GET"}}`,
},
{
name: "BlacklistToggled",
event: zoraxy_plugin.Event{
Name: zoraxy_plugin.EventBlacklistToggled,
Timestamp: timestamp,
Data: &zoraxy_plugin.BlacklistToggledEvent{
RuleID: "rule123",
Enabled: true,
},
},
expectedJson: `{"name":"blacklistToggled","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"rule_id":"rule123","enabled":true}}`,
},
{
name: "AccessRuleCreated",
event: zoraxy_plugin.Event{
Name: zoraxy_plugin.EventAccessRuleCreated,
Timestamp: timestamp,
Data: &zoraxy_plugin.AccessRuleCreatedEvent{
ID: "rule456",
Name: "New Access Rule",
Desc: "A dummy access rule",
BlacklistEnabled: true,
WhitelistEnabled: false,
},
},
expectedJson: `{"name":"accessRuleCreated","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"id":"rule456","name":"New Access Rule","desc":"A dummy access rule","blacklist_enabled":true,"whitelist_enabled":false}}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Serialize the event
jsonData, err := json.Marshal(test.event)
if err != nil {
t.Fatalf("Failed to serialize event: %v", err)
}
// Compare the serialized JSON with the expected JSON
if string(jsonData) != test.expectedJson {
t.Fatalf("Unexpected JSON output.\nGot: %s\nWant: %s", jsonData, test.expectedJson)
}
// Deserialize the JSON back into an event
var deserializedEvent zoraxy_plugin.Event
if err := zoraxy_plugin.ParseEvent(jsonData, &deserializedEvent); err != nil {
t.Fatalf("Failed to parse event: %v", err)
}
})
}
}

View File

@@ -157,9 +157,9 @@ func (m *Manager) StartPlugin(pluginID string) error {
eventType := zoraxyPlugin.EventName(eventName)
err := EventSystem.Subscribe(thisPlugin.Spec.ID, eventType)
if err != nil {
m.Log("Failed to subscribe plugin "+thisPlugin.Spec.Name+" to event "+eventName, err)
m.Log("Failed to subscribe plugin "+thisPlugin.Spec.Name+" to event "+string(eventName), err)
} else {
m.Log("Subscribed plugin "+thisPlugin.Spec.Name+" to event "+eventName, nil)
m.Log("Subscribed plugin "+thisPlugin.Spec.Name+" to event "+string(eventName), nil)
}
}
}

View File

@@ -1,7 +1,8 @@
package zoraxy_plugin
import (
"time"
"encoding/json"
"fmt"
)
// EventName represents the type of event
@@ -15,8 +16,8 @@ type EventPayload interface {
// Event represents a system event
type Event struct {
Name EventName `json:"type"`
Timestamp time.Time `json:"timestamp"`
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
Data EventPayload `json:"data"`
}
@@ -59,7 +60,7 @@ func (e *BlacklistToggledEvent) GetName() EventName {
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
Name string `json:"name"`
Desc string `json:"description"`
Desc string `json:"desc"`
BlacklistEnabled bool `json:"blacklist_enabled"`
WhitelistEnabled bool `json:"whitelist_enabled"`
}
@@ -67,3 +68,44 @@ type AccessRuleCreatedEvent struct {
func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
}
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
case EventBlacklistedIPBlocked:
var payload BlacklistedIPBlockedEvent
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload
case EventBlacklistToggled:
var payload BlacklistToggledEvent
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload
case EventAccessRuleCreated:
var payload AccessRuleCreatedEvent
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}
return nil
}

View File

@@ -102,8 +102,8 @@ type IntroSpect struct {
UIPath string `json:"ui_path"` //UI path of your plugin (e.g. /ui), will proxy the whole subpath tree to Zoraxy Web UI as plugin UI
/* Subscriptions Settings */
SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered
SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, paired with comments describing how the event is used, see Zoraxy documentation for more details
SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered
SubscriptionsEvents map[EventName]string `json:"subscriptions_events"` //Subscriptions events of your plugin, paired with comments describing how the event is used, see Zoraxy documentation for more details
/* API Access Control */
PermittedAPIEndpoints []PermittedAPIEndpoint `json:"permitted_api_endpoints"` //List of API endpoints this plugin can access, and a description of why the plugin needs to access this endpoint