- Add comprehensive loop prevention at source level for all bridges: - XMPP bridge: Skip messages from own XMPP connection user - Mattermost bridge: Skip messages from bot user and remote users - Remove cache from getOrCreateRemoteUser method for simplified user management - Improve XMPP client architecture with direct handler delegation: - Add SetMessageHandler and GetJID methods to XMPP client - Move protocol normalization methods to client level - Implement handleIncomingXMPPMessage in XMPP bridge for business logic - Fix message direction handling in XMPP message handler - Add remote user invitation to shared channels via InviteRemoteToChannel API - Clean up unused code and improve code formatting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
563 lines
18 KiB
Go
563 lines
18 KiB
Go
package bridge
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
|
mmModel "github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
// BridgeManager manages multiple bridge instances
|
|
type BridgeManager struct {
|
|
bridges map[string]model.Bridge
|
|
mu sync.RWMutex
|
|
logger logger.Logger
|
|
api plugin.API
|
|
remoteID string
|
|
messageBus model.MessageBus
|
|
routingCtx context.Context
|
|
routingCancel context.CancelFunc
|
|
routingWg sync.WaitGroup
|
|
}
|
|
|
|
// NewBridgeManager creates a new bridge manager
|
|
func NewBridgeManager(logger logger.Logger, api plugin.API, remoteID string) model.BridgeManager {
|
|
if logger == nil {
|
|
panic("logger cannot be nil")
|
|
}
|
|
if api == nil {
|
|
panic("plugin API cannot be nil")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &BridgeManager{
|
|
bridges: make(map[string]model.Bridge),
|
|
logger: logger,
|
|
api: api,
|
|
remoteID: remoteID,
|
|
messageBus: NewMessageBus(logger),
|
|
routingCtx: ctx,
|
|
routingCancel: cancel,
|
|
}
|
|
}
|
|
|
|
// RegisterBridge registers a bridge with the manager
|
|
func (m *BridgeManager) RegisterBridge(name string, bridge model.Bridge) error {
|
|
if name == "" {
|
|
return fmt.Errorf("bridge name cannot be empty")
|
|
}
|
|
if bridge == nil {
|
|
return fmt.Errorf("bridge cannot be nil")
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, exists := m.bridges[name]; exists {
|
|
return fmt.Errorf("bridge '%s' is already registered", name)
|
|
}
|
|
|
|
m.bridges[name] = bridge
|
|
m.logger.LogInfo("Bridge registered", "bridge_id", name)
|
|
|
|
// Subscribe bridge to message bus
|
|
go m.startBridgeMessageHandler(name, bridge)
|
|
|
|
return nil
|
|
}
|
|
|
|
// StartBridge starts a specific bridge
|
|
func (m *BridgeManager) StartBridge(name string) error {
|
|
m.mu.RLock()
|
|
bridge, exists := m.bridges[name]
|
|
m.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
m.logger.LogInfo("Starting bridge", "bridge_id", name)
|
|
|
|
if err := bridge.Start(); err != nil {
|
|
m.logger.LogError("Failed to start bridge", "bridge_id", name, "error", err)
|
|
return fmt.Errorf("failed to start bridge '%s': %w", name, err)
|
|
}
|
|
|
|
m.logger.LogInfo("Bridge started successfully", "bridge_id", name)
|
|
return nil
|
|
}
|
|
|
|
// StopBridge stops a specific bridge
|
|
func (m *BridgeManager) StopBridge(name string) error {
|
|
m.mu.RLock()
|
|
bridge, exists := m.bridges[name]
|
|
m.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
m.logger.LogInfo("Stopping bridge", "bridge_id", name)
|
|
|
|
if err := bridge.Stop(); err != nil {
|
|
m.logger.LogError("Failed to stop bridge", "bridge_id", name, "error", err)
|
|
return fmt.Errorf("failed to stop bridge '%s': %w", name, err)
|
|
}
|
|
|
|
m.logger.LogInfo("Bridge stopped successfully", "bridge_id", name)
|
|
return nil
|
|
}
|
|
|
|
// UnregisterBridge removes a bridge from the manager
|
|
func (m *BridgeManager) UnregisterBridge(name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
bridge, exists := m.bridges[name]
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
// Stop the bridge before unregistering
|
|
if bridge.IsConnected() {
|
|
if err := bridge.Stop(); err != nil {
|
|
m.logger.LogWarn("Failed to stop bridge during unregistration", "bridge_id", name, "error", err)
|
|
}
|
|
}
|
|
|
|
delete(m.bridges, name)
|
|
m.logger.LogInfo("Bridge unregistered", "bridge_id", name)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetBridge retrieves a bridge by name
|
|
func (m *BridgeManager) GetBridge(name string) (model.Bridge, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
bridge, exists := m.bridges[name]
|
|
if !exists {
|
|
return nil, fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
return bridge, nil
|
|
}
|
|
|
|
// ListBridges returns a list of all registered bridge names
|
|
func (m *BridgeManager) ListBridges() []string {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
bridges := make([]string, 0, len(m.bridges))
|
|
for name := range m.bridges {
|
|
bridges = append(bridges, name)
|
|
}
|
|
|
|
return bridges
|
|
}
|
|
|
|
// Start starts the bridge manager and message routing system
|
|
func (m *BridgeManager) Start() error {
|
|
m.logger.LogInfo("Starting bridge manager")
|
|
|
|
// Start the message routing system
|
|
if err := m.StartMessageRouting(); err != nil {
|
|
m.logger.LogError("Failed to start message routing", "error", err)
|
|
return fmt.Errorf("failed to start message routing: %w", err)
|
|
}
|
|
|
|
m.logger.LogInfo("Bridge manager started successfully")
|
|
return nil
|
|
}
|
|
|
|
// HasBridge checks if a bridge with the given name is registered
|
|
func (m *BridgeManager) HasBridge(name string) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
_, exists := m.bridges[name]
|
|
return exists
|
|
}
|
|
|
|
// HasBridges checks if any bridges are registered
|
|
func (m *BridgeManager) HasBridges() bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
return len(m.bridges) > 0
|
|
}
|
|
|
|
// Shutdown stops and unregisters all bridges
|
|
func (m *BridgeManager) Shutdown() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.logger.LogInfo("Shutting down bridge manager", "bridge_count", len(m.bridges))
|
|
|
|
// Stop message routing first
|
|
if err := m.StopMessageRouting(); err != nil {
|
|
m.logger.LogError("Failed to stop message routing during shutdown", "error", err)
|
|
}
|
|
|
|
var errors []error
|
|
for name, bridge := range m.bridges {
|
|
if bridge.IsConnected() {
|
|
if err := bridge.Stop(); err != nil {
|
|
errors = append(errors, fmt.Errorf("failed to stop bridge '%s': %w", name, err))
|
|
m.logger.LogError("Failed to stop bridge during shutdown", "bridge_id", name, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear all bridges
|
|
m.bridges = make(map[string]model.Bridge)
|
|
|
|
m.logger.LogInfo("Bridge manager shutdown complete")
|
|
|
|
if len(errors) > 0 {
|
|
return fmt.Errorf("shutdown completed with errors: %v", errors)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// OnPluginConfigurationChange propagates configuration changes to all registered bridges
|
|
func (m *BridgeManager) OnPluginConfigurationChange(config *config.Configuration) error {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if len(m.bridges) == 0 {
|
|
return nil
|
|
}
|
|
|
|
m.logger.LogInfo("Plugin configuration changed, propagating to bridges", "bridge_count", len(m.bridges))
|
|
|
|
var errors []error
|
|
for name, bridge := range m.bridges {
|
|
if err := bridge.UpdateConfiguration(config); err != nil {
|
|
errors = append(errors, fmt.Errorf("failed to update configuration for bridge '%s': %w", name, err))
|
|
m.logger.LogError("Failed to update bridge configuration", "bridge_id", name, "error", err)
|
|
} else {
|
|
m.logger.LogDebug("Successfully updated bridge configuration", "bridge_id", name)
|
|
}
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
return fmt.Errorf("configuration update completed with errors: %v", errors)
|
|
}
|
|
|
|
m.logger.LogInfo("Configuration changes propagated to all bridges")
|
|
return nil
|
|
}
|
|
|
|
// CreateChannelMapping handles the creation of a channel mapping by calling the appropriate bridge
|
|
func (m *BridgeManager) CreateChannelMapping(req model.CreateChannelMappingRequest) error {
|
|
// Validate request
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid mapping request: %w", err)
|
|
}
|
|
|
|
m.logger.LogDebug("Creating channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "bridge_channel_id", req.BridgeChannelID, "user_id", req.UserID, "team_id", req.TeamID)
|
|
|
|
// Get the specific bridge
|
|
bridge, err := m.GetBridge(req.BridgeName)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get bridge", "bridge_name", req.BridgeName, "error", err)
|
|
return fmt.Errorf("failed to get bridge '%s': %w", req.BridgeName, err)
|
|
}
|
|
|
|
// Check if bridge is connected
|
|
if !bridge.IsConnected() {
|
|
return fmt.Errorf("bridge '%s' is not connected", req.BridgeName)
|
|
}
|
|
|
|
// Check if channel mapping already exists on the bridge
|
|
existingChannelID, err := bridge.GetChannelMapping(req.BridgeChannelID)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to check channel mapping", "bridge_channel_id", req.BridgeChannelID, "error", err)
|
|
return fmt.Errorf("failed to check channel mapping: %w", err)
|
|
}
|
|
if existingChannelID != "" {
|
|
m.logger.LogWarn("Channel already mapped to another channel",
|
|
"bridge_channel_id", req.BridgeChannelID,
|
|
"existing_channel_id", existingChannelID,
|
|
"requested_channel_id", req.ChannelID)
|
|
return fmt.Errorf("channel '%s' is already mapped to channel '%s'", req.BridgeChannelID, existingChannelID)
|
|
}
|
|
|
|
// NEW: Check if room exists on target bridge
|
|
channelExists, err := bridge.ChannelMappingExists(req.BridgeChannelID)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to check channel existence", "bridge_channel_id", req.BridgeChannelID, "error", err)
|
|
return fmt.Errorf("failed to check channel existence: %w", err)
|
|
}
|
|
if !channelExists {
|
|
m.logger.LogWarn("Channel does not exist on bridge",
|
|
"bridge_channel_id", req.BridgeChannelID,
|
|
"bridge_name", req.BridgeName)
|
|
return fmt.Errorf("channel '%s' does not exist on %s bridge", req.BridgeChannelID, req.BridgeName)
|
|
}
|
|
|
|
m.logger.LogDebug("Channel validation passed",
|
|
"bridge_channel_id", req.BridgeChannelID,
|
|
"bridge_name", req.BridgeName,
|
|
"channel_exists", channelExists,
|
|
"already_mapped", false)
|
|
|
|
// Create the channel mapping on the receiving bridge
|
|
if err = bridge.CreateChannelMapping(req.ChannelID, req.BridgeChannelID); err != nil {
|
|
m.logger.LogError("Failed to create channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "bridge_channel_id", req.BridgeChannelID, "error", err)
|
|
return fmt.Errorf("failed to create channel mapping for bridge '%s': %w", req.BridgeName, err)
|
|
}
|
|
|
|
mattermostBridge, err := m.GetBridge("mattermost")
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
|
|
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Create the channel mapping in the Mattermost bridge
|
|
if err = mattermostBridge.CreateChannelMapping(req.ChannelID, req.BridgeChannelID); err != nil {
|
|
m.logger.LogError("Failed to create channel mapping in Mattermost bridge", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "bridge_channel_id", req.BridgeChannelID, "error", err)
|
|
return fmt.Errorf("failed to create channel mapping in Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Share the channel using Mattermost's shared channels API
|
|
if err = m.shareChannel(req); err != nil {
|
|
m.logger.LogError("Failed to share channel", "channel_id", req.ChannelID, "bridge_channel_id", req.BridgeChannelID, "error", err)
|
|
// Don't fail the entire operation if sharing fails, but log the error
|
|
m.logger.LogWarn("Channel mapping created but sharing failed - channel may not sync properly")
|
|
}
|
|
|
|
m.logger.LogInfo("Successfully created channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "bridge_channel_id", req.BridgeChannelID)
|
|
return nil
|
|
}
|
|
|
|
// DeleteChannepMapping handles the deletion of a channel mapping by calling the appropriate bridges
|
|
func (m *BridgeManager) DeleteChannepMapping(req model.DeleteChannelMappingRequest) error {
|
|
// Validate request
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid delete request: %w", err)
|
|
}
|
|
|
|
m.logger.LogDebug("Deleting channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "user_id", req.UserID, "team_id", req.TeamID)
|
|
|
|
// Get the specific bridge
|
|
bridge, err := m.GetBridge(req.BridgeName)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get bridge", "bridge_name", req.BridgeName, "error", err)
|
|
return fmt.Errorf("failed to get bridge '%s': %w", req.BridgeName, err)
|
|
}
|
|
|
|
// Check if bridge is connected
|
|
if !bridge.IsConnected() {
|
|
return fmt.Errorf("bridge '%s' is not connected", req.BridgeName)
|
|
}
|
|
|
|
// Delete the channel mapping from the specific bridge
|
|
if err = bridge.DeleteChannelMapping(req.ChannelID); err != nil {
|
|
m.logger.LogError("Failed to delete channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "error", err)
|
|
return fmt.Errorf("failed to delete channel mapping for bridge '%s': %w", req.BridgeName, err)
|
|
}
|
|
|
|
// Also delete from Mattermost bridge to clean up reverse mappings
|
|
mattermostBridge, err := m.GetBridge("mattermost")
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
|
|
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Delete the channel mapping from the Mattermost bridge
|
|
if err = mattermostBridge.DeleteChannelMapping(req.ChannelID); err != nil {
|
|
m.logger.LogError("Failed to delete channel mapping from Mattermost bridge", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "error", err)
|
|
return fmt.Errorf("failed to delete channel mapping from Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Unshare the channel using Mattermost's shared channels API
|
|
if err = m.unshareChannel(req.ChannelID); err != nil {
|
|
m.logger.LogError("Failed to unshare channel", "channel_id", req.ChannelID, "error", err)
|
|
// Don't fail the entire operation if unsharing fails, but log the error
|
|
m.logger.LogWarn("Channel mapping deleted but unsharing failed - channel may still appear as shared")
|
|
}
|
|
|
|
m.logger.LogInfo("Successfully deleted channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName)
|
|
return nil
|
|
}
|
|
|
|
// shareChannel creates a shared channel configuration using the Mattermost API
|
|
func (m *BridgeManager) shareChannel(req model.CreateChannelMappingRequest) error {
|
|
if m.remoteID == "" {
|
|
return fmt.Errorf("remote ID not set - plugin not registered for shared channels")
|
|
}
|
|
|
|
// Create SharedChannel configuration
|
|
sharedChannel := &mmModel.SharedChannel{
|
|
ChannelId: req.ChannelID,
|
|
TeamId: req.TeamID,
|
|
Home: true,
|
|
ReadOnly: false,
|
|
ShareName: model.SanitizeShareName(fmt.Sprintf("bridge-%s", req.BridgeChannelID)),
|
|
ShareDisplayName: fmt.Sprintf("Bridge: %s", req.BridgeChannelID),
|
|
SharePurpose: fmt.Sprintf("Shared channel bridged to %s", req.BridgeChannelID),
|
|
ShareHeader: "test header",
|
|
CreatorId: req.UserID,
|
|
RemoteId: "",
|
|
}
|
|
|
|
// Share the channel
|
|
sharedChannel, err := m.api.ShareChannel(sharedChannel)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to share channel via API: %w", err)
|
|
}
|
|
|
|
m.logger.LogInfo("Successfully shared channel", "channel_id", req.ChannelID, "shared_channel_id", sharedChannel.ChannelId)
|
|
return nil
|
|
}
|
|
|
|
// unshareChannel removes shared channel configuration using the Mattermost API
|
|
func (m *BridgeManager) unshareChannel(channelID string) error {
|
|
// Unshare the channel
|
|
unshared, err := m.api.UnshareChannel(channelID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unshare channel via API: %w", err)
|
|
}
|
|
|
|
if !unshared {
|
|
m.logger.LogWarn("Channel was not shared or already unshared", "channel_id", channelID)
|
|
} else {
|
|
m.logger.LogInfo("Successfully unshared channel", "channel_id", channelID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// startBridgeMessageHandler starts message handling for a specific bridge
|
|
func (m *BridgeManager) startBridgeMessageHandler(bridgeID string, bridge model.Bridge) {
|
|
m.logger.LogDebug("Starting message handler for bridge", "bridge_id", bridgeID)
|
|
|
|
// Subscribe to message bus
|
|
messageChannel := m.messageBus.Subscribe(bridgeID)
|
|
|
|
// Start message routing goroutine
|
|
m.routingWg.Add(1)
|
|
go func() {
|
|
defer m.routingWg.Done()
|
|
defer m.logger.LogDebug("Message handler stopped for bridge", "bridge_id", bridgeID)
|
|
|
|
for {
|
|
select {
|
|
case msg, ok := <-messageChannel:
|
|
if !ok {
|
|
m.logger.LogDebug("Message channel closed for bridge", "bridge_id", bridgeID)
|
|
return
|
|
}
|
|
|
|
if err := m.handleBridgeMessage(bridgeID, bridge, msg); err != nil {
|
|
m.logger.LogError("Failed to handle message for bridge",
|
|
"bridge_id", bridgeID,
|
|
"source_bridge", msg.SourceBridge,
|
|
"error", err)
|
|
}
|
|
|
|
case <-m.routingCtx.Done():
|
|
m.logger.LogDebug("Context cancelled, stopping message handler", "bridge_id", bridgeID)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Listen to bridge's outgoing messages
|
|
m.routingWg.Add(1)
|
|
go func() {
|
|
defer m.routingWg.Done()
|
|
defer m.logger.LogDebug("Bridge message listener stopped", "bridge_id", bridgeID)
|
|
|
|
bridgeMessageChannel := bridge.GetMessageChannel()
|
|
for {
|
|
select {
|
|
case msg, ok := <-bridgeMessageChannel:
|
|
if !ok {
|
|
m.logger.LogDebug("Bridge message channel closed", "bridge_id", bridgeID)
|
|
return
|
|
}
|
|
|
|
if err := m.messageBus.Publish(msg); err != nil {
|
|
m.logger.LogError("Failed to publish message from bridge",
|
|
"bridge_id", bridgeID,
|
|
"direction", msg.Direction,
|
|
"error", err)
|
|
}
|
|
|
|
case <-m.routingCtx.Done():
|
|
m.logger.LogDebug("Context cancelled, stopping bridge listener", "bridge_id", bridgeID)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// handleBridgeMessage processes an incoming message for a specific bridge
|
|
func (m *BridgeManager) handleBridgeMessage(bridgeID string, bridge model.Bridge, msg *model.DirectionalMessage) error {
|
|
m.logger.LogDebug("Handling message for bridge",
|
|
"target_bridge", bridgeID,
|
|
"source_bridge", msg.SourceBridge,
|
|
"direction", msg.Direction,
|
|
"channel_id", msg.SourceChannelID)
|
|
|
|
// Get the bridge's message handler
|
|
handler := bridge.GetMessageHandler()
|
|
if handler == nil {
|
|
return fmt.Errorf("bridge %s does not have a message handler", bridgeID)
|
|
}
|
|
|
|
// Check if the handler can process this message
|
|
if !handler.CanHandleMessage(msg.BridgeMessage) {
|
|
m.logger.LogDebug("Bridge cannot handle message",
|
|
"bridge_id", bridgeID,
|
|
"message_type", msg.MessageType)
|
|
return nil // Not an error, just skip
|
|
}
|
|
|
|
// Process the message
|
|
return handler.ProcessMessage(msg)
|
|
}
|
|
|
|
// StartMessageRouting starts the message bus and routing system
|
|
func (m *BridgeManager) StartMessageRouting() error {
|
|
m.logger.LogInfo("Starting message routing system")
|
|
return m.messageBus.Start()
|
|
}
|
|
|
|
// StopMessageRouting stops the message bus and routing system
|
|
func (m *BridgeManager) StopMessageRouting() error {
|
|
m.logger.LogInfo("Stopping message routing system")
|
|
|
|
// Cancel routing context
|
|
if m.routingCancel != nil {
|
|
m.routingCancel()
|
|
}
|
|
|
|
// Wait for all routing goroutines to finish
|
|
m.routingWg.Wait()
|
|
|
|
// Stop the message bus
|
|
return m.messageBus.Stop()
|
|
}
|
|
|
|
// PublishMessage publishes a message to the message bus for routing to target bridges
|
|
func (m *BridgeManager) PublishMessage(msg *model.DirectionalMessage) error {
|
|
m.logger.LogDebug("Publishing message to message bus",
|
|
"source_bridge", msg.SourceBridge,
|
|
"direction", msg.Direction,
|
|
"target_bridges", msg.TargetBridges,
|
|
"message_id", msg.MessageID)
|
|
|
|
return m.messageBus.Publish(msg)
|
|
}
|