chore: fix lint issues
This commit is contained in:
parent
17ea21a579
commit
7c37953c28
20 changed files with 136 additions and 131 deletions
|
@ -124,7 +124,7 @@ func testXMPPClient(config *Config) error {
|
||||||
log.Printf("Using insecure TLS configuration (skipping certificate verification)")
|
log.Printf("Using insecure TLS configuration (skipping certificate verification)")
|
||||||
}
|
}
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true, //nolint:gosec // This is a testing tool for development environments
|
||||||
}
|
}
|
||||||
client = xmpp.NewClientWithTLS(
|
client = xmpp.NewClientWithTLS(
|
||||||
config.Server,
|
config.Server,
|
||||||
|
@ -302,7 +302,7 @@ func testMUCOperations(client *xmpp.Client, config *Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
_, err = client.SendMessage(messageReq)
|
_, err = client.SendMessage(&messageReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send test message to room %s: %w", config.TestRoom, err)
|
return fmt.Errorf("failed to send test message to room %s: %w", config.TestRoom, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
"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/logger"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
"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
|
// BridgeManager manages multiple bridge instances
|
||||||
|
//
|
||||||
|
//nolint:revive // BridgeManager is clearer than Manager in this context
|
||||||
type BridgeManager struct {
|
type BridgeManager struct {
|
||||||
bridges map[string]model.Bridge
|
bridges map[string]model.Bridge
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
@ -26,22 +29,15 @@ type BridgeManager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBridgeManager creates a new bridge manager
|
// NewBridgeManager creates a new bridge manager
|
||||||
func NewBridgeManager(logger logger.Logger, api plugin.API, remoteID string) model.BridgeManager {
|
func NewBridgeManager(log 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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &BridgeManager{
|
return &BridgeManager{
|
||||||
bridges: make(map[string]model.Bridge),
|
bridges: make(map[string]model.Bridge),
|
||||||
logger: logger,
|
logger: log,
|
||||||
api: api,
|
api: api,
|
||||||
remoteID: remoteID,
|
remoteID: remoteID,
|
||||||
messageBus: NewMessageBus(logger),
|
messageBus: NewMessageBus(log),
|
||||||
routingCtx: ctx,
|
routingCtx: ctx,
|
||||||
routingCancel: cancel,
|
routingCancel: cancel,
|
||||||
}
|
}
|
||||||
|
@ -229,7 +225,7 @@ func (m *BridgeManager) Shutdown() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPluginConfigurationChange propagates configuration changes to all registered bridges
|
// OnPluginConfigurationChange propagates configuration changes to all registered bridges
|
||||||
func (m *BridgeManager) OnPluginConfigurationChange(config *config.Configuration) error {
|
func (m *BridgeManager) OnPluginConfigurationChange(cfg *config.Configuration) error {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
@ -241,7 +237,7 @@ func (m *BridgeManager) OnPluginConfigurationChange(config *config.Configuration
|
||||||
|
|
||||||
var errors []error
|
var errors []error
|
||||||
for name, bridge := range m.bridges {
|
for name, bridge := range m.bridges {
|
||||||
if err := bridge.UpdateConfiguration(config); err != nil {
|
if err := bridge.UpdateConfiguration(cfg); err != nil {
|
||||||
errors = append(errors, fmt.Errorf("failed to update configuration for bridge '%s': %w", name, err))
|
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)
|
m.logger.LogError("Failed to update bridge configuration", "bridge_id", name, "error", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -258,7 +254,7 @@ func (m *BridgeManager) OnPluginConfigurationChange(config *config.Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateChannelMapping handles the creation of a channel mapping by calling the appropriate bridge
|
// CreateChannelMapping handles the creation of a channel mapping by calling the appropriate bridge
|
||||||
func (m *BridgeManager) CreateChannelMapping(req model.CreateChannelMappingRequest) error {
|
func (m *BridgeManager) CreateChannelMapping(req *model.CreateChannelMappingRequest) error {
|
||||||
// Validate request
|
// Validate request
|
||||||
if err := req.Validate(); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
return fmt.Errorf("invalid mapping request: %w", err)
|
return fmt.Errorf("invalid mapping request: %w", err)
|
||||||
|
@ -392,7 +388,7 @@ func (m *BridgeManager) DeleteChannepMapping(req model.DeleteChannelMappingReque
|
||||||
}
|
}
|
||||||
|
|
||||||
// shareChannel creates a shared channel configuration using the Mattermost API
|
// shareChannel creates a shared channel configuration using the Mattermost API
|
||||||
func (m *BridgeManager) shareChannel(req model.CreateChannelMappingRequest) error {
|
func (m *BridgeManager) shareChannel(req *model.CreateChannelMappingRequest) error {
|
||||||
if m.remoteID == "" {
|
if m.remoteID == "" {
|
||||||
return fmt.Errorf("remote ID not set - plugin not registered for shared channels")
|
return fmt.Errorf("remote ID not set - plugin not registered for shared channels")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
"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/logger"
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -49,12 +50,12 @@ type mattermostBridge struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBridge creates a new Mattermost bridge
|
// NewBridge creates a new Mattermost bridge
|
||||||
func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *config.Configuration, botUserID, bridgeID, remoteID string) pluginModel.Bridge {
|
func NewBridge(log logger.Logger, api plugin.API, store kvstore.KVStore, cfg *config.Configuration, botUserID, bridgeID, remoteID string) pluginModel.Bridge {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
b := &mattermostBridge{
|
b := &mattermostBridge{
|
||||||
logger: log,
|
logger: log,
|
||||||
api: api,
|
api: api,
|
||||||
kvstore: kvstore,
|
kvstore: store,
|
||||||
botUserID: botUserID,
|
botUserID: botUserID,
|
||||||
bridgeID: bridgeID,
|
bridgeID: bridgeID,
|
||||||
remoteID: remoteID,
|
remoteID: remoteID,
|
||||||
|
@ -73,13 +74,6 @@ func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfiguration safely retrieves the current configuration
|
|
||||||
func (b *mattermostBridge) getConfiguration() *config.Configuration {
|
|
||||||
b.configMu.RLock()
|
|
||||||
defer b.configMu.RUnlock()
|
|
||||||
return b.config
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConfiguration updates the bridge configuration
|
// UpdateConfiguration updates the bridge configuration
|
||||||
func (b *mattermostBridge) UpdateConfiguration(cfg *config.Configuration) error {
|
func (b *mattermostBridge) UpdateConfiguration(cfg *config.Configuration) error {
|
||||||
// Validate configuration using built-in validation
|
// Validate configuration using built-in validation
|
||||||
|
@ -102,10 +96,10 @@ func (b *mattermostBridge) Start() error {
|
||||||
b.logger.LogDebug("Starting Mattermost bridge")
|
b.logger.LogDebug("Starting Mattermost bridge")
|
||||||
|
|
||||||
b.configMu.RLock()
|
b.configMu.RLock()
|
||||||
config := b.config
|
cfg := b.config
|
||||||
b.configMu.RUnlock()
|
b.configMu.RUnlock()
|
||||||
|
|
||||||
if config == nil {
|
if cfg == nil {
|
||||||
return fmt.Errorf("bridge configuration not set")
|
return fmt.Errorf("bridge configuration not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,11 +201,11 @@ func (b *mattermostBridge) IsConnected() bool {
|
||||||
// Ping actively tests the Mattermost API connectivity
|
// Ping actively tests the Mattermost API connectivity
|
||||||
func (b *mattermostBridge) Ping() error {
|
func (b *mattermostBridge) Ping() error {
|
||||||
if !b.connected.Load() {
|
if !b.connected.Load() {
|
||||||
return fmt.Errorf("Mattermost bridge is not connected")
|
return fmt.Errorf("mattermost bridge is not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.api == nil {
|
if b.api == nil {
|
||||||
return fmt.Errorf("Mattermost API not initialized")
|
return fmt.Errorf("mattermost API not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logger.LogDebug("Testing Mattermost bridge connectivity with API ping")
|
b.logger.LogDebug("Testing Mattermost bridge connectivity with API ping")
|
||||||
|
@ -221,7 +215,7 @@ func (b *mattermostBridge) Ping() error {
|
||||||
version := b.api.GetServerVersion()
|
version := b.api.GetServerVersion()
|
||||||
if version == "" {
|
if version == "" {
|
||||||
b.logger.LogWarn("Mattermost bridge ping returned empty version")
|
b.logger.LogWarn("Mattermost bridge ping returned empty version")
|
||||||
return fmt.Errorf("Mattermost API ping returned empty server version")
|
return fmt.Errorf("mattermost API ping returned empty server version")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logger.LogDebug("Mattermost bridge ping successful", "server_version", version)
|
b.logger.LogDebug("Mattermost bridge ping successful", "server_version", version)
|
||||||
|
@ -313,7 +307,7 @@ func (b *mattermostBridge) DeleteChannelMapping(channelID string) error {
|
||||||
// ChannelMappingExists checks if a Mattermost channel exists on the server
|
// ChannelMappingExists checks if a Mattermost channel exists on the server
|
||||||
func (b *mattermostBridge) ChannelMappingExists(roomID string) (bool, error) {
|
func (b *mattermostBridge) ChannelMappingExists(roomID string) (bool, error) {
|
||||||
if b.api == nil {
|
if b.api == nil {
|
||||||
return false, fmt.Errorf("Mattermost API not initialized")
|
return false, fmt.Errorf("mattermost API not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logger.LogDebug("Checking if Mattermost channel exists", "channel_id", roomID)
|
b.logger.LogDebug("Checking if Mattermost channel exists", "channel_id", roomID)
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost/server/public/model"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
mmModel "github.com/mattermost/mattermost/server/public/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// mattermostMessageHandler handles incoming messages for the Mattermost bridge
|
// mattermostMessageHandler handles incoming messages for the Mattermost bridge
|
||||||
|
@ -59,7 +60,7 @@ func (h *mattermostMessageHandler) GetSupportedMessageTypes() []string {
|
||||||
// postMessageToMattermost posts a message to a Mattermost channel
|
// postMessageToMattermost posts a message to a Mattermost channel
|
||||||
func (h *mattermostMessageHandler) postMessageToMattermost(msg *pluginModel.BridgeMessage) error {
|
func (h *mattermostMessageHandler) postMessageToMattermost(msg *pluginModel.BridgeMessage) error {
|
||||||
if h.bridge.api == nil {
|
if h.bridge.api == nil {
|
||||||
return fmt.Errorf("Mattermost API not initialized")
|
return fmt.Errorf("mattermost API not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Mattermost channel ID from the channel mapping using the source bridge name
|
// Get the Mattermost channel ID from the channel mapping using the source bridge name
|
||||||
|
@ -243,7 +244,7 @@ func (r *mattermostUserResolver) ResolveUser(externalUserID string) (*pluginMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, fmt.Errorf("Mattermost user not found: %s", externalUserID)
|
return nil, fmt.Errorf("mattermost user not found: %s", externalUserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pluginModel.ExternalUser{
|
return &pluginModel.ExternalUser{
|
||||||
|
|
|
@ -6,14 +6,17 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
"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/logger"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
mmModel "github.com/mattermost/mattermost/server/public/model"
|
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MattermostUser represents a Mattermost user that implements the BridgeUser interface
|
// MattermostUser represents a Mattermost user that implements the BridgeUser interface
|
||||||
|
//
|
||||||
|
//nolint:revive // MattermostUser is clearer than User in this context
|
||||||
type MattermostUser struct {
|
type MattermostUser struct {
|
||||||
// User identity
|
// User identity
|
||||||
id string
|
id string
|
||||||
|
@ -40,7 +43,7 @@ type MattermostUser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMattermostUser creates a new Mattermost user
|
// NewMattermostUser creates a new Mattermost user
|
||||||
func NewMattermostUser(id, displayName, username, email string, api plugin.API, cfg *config.Configuration, logger logger.Logger) *MattermostUser {
|
func NewMattermostUser(id, displayName, username, email string, api plugin.API, cfg *config.Configuration, log logger.Logger) *MattermostUser {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &MattermostUser{
|
return &MattermostUser{
|
||||||
|
@ -53,7 +56,7 @@ func NewMattermostUser(id, displayName, username, email string, api plugin.API,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
logger: logger,
|
logger: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +72,7 @@ func (u *MattermostUser) Validate() error {
|
||||||
return fmt.Errorf("configuration cannot be nil")
|
return fmt.Errorf("configuration cannot be nil")
|
||||||
}
|
}
|
||||||
if u.api == nil {
|
if u.api == nil {
|
||||||
return fmt.Errorf("Mattermost API cannot be nil")
|
return fmt.Errorf("mattermost API cannot be nil")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -192,13 +195,13 @@ func (u *MattermostUser) IsConnected() bool {
|
||||||
|
|
||||||
func (u *MattermostUser) Ping() error {
|
func (u *MattermostUser) Ping() error {
|
||||||
if u.api == nil {
|
if u.api == nil {
|
||||||
return fmt.Errorf("Mattermost API not initialized for user %s", u.id)
|
return fmt.Errorf("mattermost API not initialized for user %s", u.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test API connectivity by getting server version
|
// Test API connectivity by getting server version
|
||||||
version := u.api.GetServerVersion()
|
version := u.api.GetServerVersion()
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return fmt.Errorf("Mattermost API ping returned empty server version for user %s", u.id)
|
return fmt.Errorf("mattermost API ping returned empty server version for user %s", u.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -207,7 +210,7 @@ func (u *MattermostUser) Ping() error {
|
||||||
// CheckChannelExists checks if a Mattermost channel exists
|
// CheckChannelExists checks if a Mattermost channel exists
|
||||||
func (u *MattermostUser) CheckChannelExists(channelID string) (bool, error) {
|
func (u *MattermostUser) CheckChannelExists(channelID string) (bool, error) {
|
||||||
if u.api == nil {
|
if u.api == nil {
|
||||||
return false, fmt.Errorf("Mattermost API not initialized for user %s", u.id)
|
return false, fmt.Errorf("mattermost API not initialized for user %s", u.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get the channel by ID
|
// Try to get the channel by ID
|
||||||
|
|
|
@ -35,7 +35,7 @@ type messageBus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessageBus creates a new message bus instance
|
// NewMessageBus creates a new message bus instance
|
||||||
func NewMessageBus(logger logger.Logger) model.MessageBus {
|
func NewMessageBus(log logger.Logger) model.MessageBus {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &messageBus{
|
return &messageBus{
|
||||||
|
@ -43,7 +43,7 @@ func NewMessageBus(logger logger.Logger) model.MessageBus {
|
||||||
subscribers: make(map[string]chan *model.DirectionalMessage),
|
subscribers: make(map[string]chan *model.DirectionalMessage),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
logger: logger,
|
logger: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,11 @@ type UserManager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserManager creates a new user manager for a specific bridge type
|
// NewUserManager creates a new user manager for a specific bridge type
|
||||||
func NewUserManager(bridgeType string, logger logger.Logger) model.BridgeUserManager {
|
func NewUserManager(bridgeType string, log logger.Logger) model.BridgeUserManager {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
return &UserManager{
|
return &UserManager{
|
||||||
bridgeType: bridgeType,
|
bridgeType: bridgeType,
|
||||||
logger: logger,
|
logger: log,
|
||||||
users: make(map[string]model.BridgeUser),
|
users: make(map[string]model.BridgeUser),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
|
|
@ -9,15 +9,16 @@ import (
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
"mellium.im/xmlstream"
|
||||||
|
"mellium.im/xmpp/stanza"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
"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/logger"
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
||||||
xmppClient "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/xmpp"
|
xmppClient "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/xmpp"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
|
||||||
"mellium.im/xmlstream"
|
|
||||||
"mellium.im/xmpp/stanza"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -55,12 +56,12 @@ type xmppBridge struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBridge creates a new XMPP bridge
|
// NewBridge creates a new XMPP bridge
|
||||||
func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *config.Configuration, bridgeID, remoteID string) pluginModel.Bridge {
|
func NewBridge(log logger.Logger, api plugin.API, store kvstore.KVStore, cfg *config.Configuration, bridgeID, remoteID string) pluginModel.Bridge {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
b := &xmppBridge{
|
b := &xmppBridge{
|
||||||
logger: log,
|
logger: log,
|
||||||
api: api,
|
api: api,
|
||||||
kvstore: kvstore,
|
kvstore: store,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
channelMappings: make(map[string]string),
|
channelMappings: make(map[string]string),
|
||||||
|
@ -87,7 +88,7 @@ func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *
|
||||||
func (b *xmppBridge) createXMPPClient(cfg *config.Configuration) *xmppClient.Client {
|
func (b *xmppBridge) createXMPPClient(cfg *config.Configuration) *xmppClient.Client {
|
||||||
// Create TLS config based on certificate verification setting
|
// Create TLS config based on certificate verification setting
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify,
|
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify, //nolint:gosec // Allow insecure TLS for testing environments
|
||||||
}
|
}
|
||||||
|
|
||||||
return xmppClient.NewClientWithTLS(
|
return xmppClient.NewClientWithTLS(
|
||||||
|
@ -154,19 +155,19 @@ func (b *xmppBridge) Start() error {
|
||||||
b.logger.LogDebug("Starting Mattermost to XMPP bridge")
|
b.logger.LogDebug("Starting Mattermost to XMPP bridge")
|
||||||
|
|
||||||
b.configMu.RLock()
|
b.configMu.RLock()
|
||||||
config := b.config
|
cfg := b.config
|
||||||
b.configMu.RUnlock()
|
b.configMu.RUnlock()
|
||||||
|
|
||||||
if config == nil {
|
if cfg == nil {
|
||||||
return fmt.Errorf("bridge configuration not set")
|
return fmt.Errorf("bridge configuration not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.EnableSync {
|
if !cfg.EnableSync {
|
||||||
b.logger.LogInfo("XMPP sync is disabled, bridge will not start")
|
b.logger.LogInfo("XMPP sync is disabled, bridge will not start")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logger.LogInfo("Starting Mattermost to XMPP bridge", "xmpp_server", config.XMPPServerURL, "username", config.XMPPUsername)
|
b.logger.LogInfo("Starting Mattermost to XMPP bridge", "xmpp_server", cfg.XMPPServerURL, "username", cfg.XMPPUsername)
|
||||||
|
|
||||||
// Connect to XMPP server
|
// Connect to XMPP server
|
||||||
if err := b.connectToXMPP(); err != nil {
|
if err := b.connectToXMPP(); err != nil {
|
||||||
|
@ -340,10 +341,10 @@ func (b *xmppBridge) connectionMonitor() {
|
||||||
// handleReconnection attempts to reconnect to XMPP and rejoin rooms
|
// handleReconnection attempts to reconnect to XMPP and rejoin rooms
|
||||||
func (b *xmppBridge) handleReconnection() {
|
func (b *xmppBridge) handleReconnection() {
|
||||||
b.configMu.RLock()
|
b.configMu.RLock()
|
||||||
config := b.config
|
cfg := b.config
|
||||||
b.configMu.RUnlock()
|
b.configMu.RUnlock()
|
||||||
|
|
||||||
if config == nil || !config.EnableSync {
|
if cfg == nil || !cfg.EnableSync {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +358,7 @@ func (b *xmppBridge) handleReconnection() {
|
||||||
// Retry connection with exponential backoff
|
// Retry connection with exponential backoff
|
||||||
maxRetries := 3
|
maxRetries := 3
|
||||||
for i := range maxRetries {
|
for i := range maxRetries {
|
||||||
backoff := time.Duration(1<<uint(i)) * time.Second
|
backoff := time.Duration(1<<i) * time.Second
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-b.ctx.Done():
|
case <-b.ctx.Done():
|
||||||
|
@ -604,6 +605,8 @@ func (b *xmppBridge) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleIncomingXMPPMessage handles incoming XMPP messages and converts them to bridge messages
|
// handleIncomingXMPPMessage handles incoming XMPP messages and converts them to bridge messages
|
||||||
|
//
|
||||||
|
//nolint:gocritic // msg parameter must match external XMPP library handler signature
|
||||||
func (b *xmppBridge) handleIncomingXMPPMessage(msg stanza.Message, t xmlstream.TokenReadEncoder) error {
|
func (b *xmppBridge) handleIncomingXMPPMessage(msg stanza.Message, t xmlstream.TokenReadEncoder) error {
|
||||||
b.logger.LogDebug("XMPP bridge handling incoming message",
|
b.logger.LogDebug("XMPP bridge handling incoming message",
|
||||||
"from", msg.From.String(),
|
"from", msg.From.String(),
|
||||||
|
|
|
@ -85,7 +85,7 @@ func (h *xmppMessageHandler) sendMessageToXMPP(msg *pluginModel.BridgeMessage) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
_, err = h.bridge.bridgeClient.SendMessage(req)
|
_, err = h.bridge.bridgeClient.SendMessage(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send message to XMPP room: %w", err)
|
return fmt.Errorf("failed to send message to XMPP room: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ func (r *xmppUserResolver) FormatUserMention(user *pluginModel.ExternalUser) str
|
||||||
func (r *xmppUserResolver) GetDisplayName(externalUserID string) string {
|
func (r *xmppUserResolver) GetDisplayName(externalUserID string) string {
|
||||||
// For XMPP JIDs, extract the local part or resource as display name
|
// For XMPP JIDs, extract the local part or resource as display name
|
||||||
// Format: user@domain/resource -> use resource or user
|
// Format: user@domain/resource -> use resource or user
|
||||||
if len(externalUserID) == 0 {
|
if externalUserID == "" {
|
||||||
return "Unknown User"
|
return "Unknown User"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// XMPPUser represents an XMPP user that implements the BridgeUser interface
|
// XMPPUser represents an XMPP user that implements the BridgeUser interface
|
||||||
|
//
|
||||||
|
//nolint:revive // XMPPUser is clearer than User in this context
|
||||||
type XMPPUser struct {
|
type XMPPUser struct {
|
||||||
// User identity
|
// User identity
|
||||||
id string
|
id string
|
||||||
|
@ -41,12 +43,12 @@ type XMPPUser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewXMPPUser creates a new XMPP user
|
// NewXMPPUser creates a new XMPP user
|
||||||
func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, logger logger.Logger) *XMPPUser {
|
func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, log logger.Logger) *XMPPUser {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Create TLS config based on certificate verification setting
|
// Create TLS config based on certificate verification setting
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify,
|
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify, //nolint:gosec // Allow insecure TLS for testing environments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create XMPP client for this user
|
// Create XMPP client for this user
|
||||||
|
@ -57,7 +59,7 @@ func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, logger
|
||||||
cfg.GetXMPPResource(),
|
cfg.GetXMPPResource(),
|
||||||
id, // Use user ID as remote ID
|
id, // Use user ID as remote ID
|
||||||
tlsConfig,
|
tlsConfig,
|
||||||
logger,
|
log,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &XMPPUser{
|
return &XMPPUser{
|
||||||
|
@ -69,7 +71,7 @@ func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, logger
|
||||||
config: cfg,
|
config: cfg,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
logger: logger,
|
logger: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +173,7 @@ func (u *XMPPUser) SendMessageToChannel(channelID, message string) error {
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := u.client.SendMessage(req)
|
_, err := u.client.SendMessage(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send message to XMPP room %s: %w", channelID, err)
|
return fmt.Errorf("failed to send message to XMPP room %s: %w", channelID, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
"github.com/mattermost/mattermost/server/public/pluginapi"
|
"github.com/mattermost/mattermost/server/public/pluginapi"
|
||||||
|
|
||||||
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
@ -183,7 +184,7 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
|
||||||
TeamID: args.TeamId,
|
TeamID: args.TeamId,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.bridgeManager.CreateChannelMapping(mappingReq)
|
err = c.bridgeManager.CreateChannelMapping(&mappingReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.formatMappingError("create", roomJID, err)
|
return c.formatMappingError("create", roomJID, err)
|
||||||
}
|
}
|
||||||
|
@ -265,11 +266,12 @@ func (c *Handler) executeStatusCommand(args *model.CommandArgs) *model.CommandRe
|
||||||
roomJID, err := bridge.GetChannelMapping(channelID)
|
roomJID, err := bridge.GetChannelMapping(channelID)
|
||||||
|
|
||||||
var mappingText string
|
var mappingText string
|
||||||
if err != nil {
|
switch {
|
||||||
|
case err != nil:
|
||||||
mappingText = fmt.Sprintf("⚠️ Error checking channel mapping: %v", err)
|
mappingText = fmt.Sprintf("⚠️ Error checking channel mapping: %v", err)
|
||||||
} else if roomJID != "" {
|
case roomJID != "":
|
||||||
mappingText = fmt.Sprintf("🔗 **Current channel mapping:** `%s`", roomJID)
|
mappingText = fmt.Sprintf("🔗 **Current channel mapping:** `%s`", roomJID)
|
||||||
} else {
|
default:
|
||||||
mappingText = "📝 **Current channel:** Not mapped to any XMPP room"
|
mappingText = "📝 **Current channel:** Not mapped to any XMPP room"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getConfiguration retrieves the active configuration under lock, making it safe to use
|
// getConfiguration retrieves the active configuration under lock, making it safe to use
|
||||||
|
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
|
||||||
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OnSharedChannelsPing is called to check if the bridge is healthy and ready to process messages
|
// OnSharedChannelsPing is called to check if the bridge is healthy and ready to process messages
|
||||||
|
@ -53,7 +54,10 @@ func (p *Plugin) OnSharedChannelsPing(remoteCluster *model.RemoteCluster) bool {
|
||||||
|
|
||||||
// OnSharedChannelsSyncMsg processes sync messages from Mattermost shared channels and routes them to XMPP
|
// OnSharedChannelsSyncMsg processes sync messages from Mattermost shared channels and routes them to XMPP
|
||||||
func (p *Plugin) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) {
|
func (p *Plugin) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) {
|
||||||
p.logger.LogDebug("🚀 OnSharedChannelsSyncMsg called", "remote_id", rc.RemoteId, "channel_id", msg.ChannelId)
|
var remoteClusterID string
|
||||||
|
if rc != nil {
|
||||||
|
remoteClusterID = rc.RemoteId
|
||||||
|
}
|
||||||
|
|
||||||
config := p.getConfiguration()
|
config := p.getConfiguration()
|
||||||
|
|
||||||
|
@ -65,11 +69,6 @@ func (p *Plugin) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteClu
|
||||||
ReactionsLastUpdateAt: now,
|
ReactionsLastUpdateAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteClusterID string
|
|
||||||
if rc != nil {
|
|
||||||
remoteClusterID = rc.RemoteId
|
|
||||||
}
|
|
||||||
|
|
||||||
p.logger.LogDebug("OnSharedChannelsSyncMsg called",
|
p.logger.LogDebug("OnSharedChannelsSyncMsg called",
|
||||||
"remote_cluster_id", remoteClusterID,
|
"remote_cluster_id", remoteClusterID,
|
||||||
"channel_id", msg.ChannelId,
|
"channel_id", msg.ChannelId,
|
||||||
|
|
|
@ -28,7 +28,7 @@ type CreateChannelMappingRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks if all required fields are present and valid
|
// Validate checks if all required fields are present and valid
|
||||||
func (r CreateChannelMappingRequest) Validate() error {
|
func (r *CreateChannelMappingRequest) Validate() error {
|
||||||
if r.ChannelID == "" {
|
if r.ChannelID == "" {
|
||||||
return fmt.Errorf("channelID cannot be empty")
|
return fmt.Errorf("channelID cannot be empty")
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ type BridgeManager interface {
|
||||||
OnPluginConfigurationChange(config *config.Configuration) error
|
OnPluginConfigurationChange(config *config.Configuration) error
|
||||||
|
|
||||||
// CreateChannelMapping is called when a channel mapping is created.
|
// CreateChannelMapping is called when a channel mapping is created.
|
||||||
CreateChannelMapping(req CreateChannelMappingRequest) error
|
CreateChannelMapping(req *CreateChannelMappingRequest) error
|
||||||
|
|
||||||
// DeleteChannepMapping is called when a channel mapping is deleted.
|
// DeleteChannepMapping is called when a channel mapping is deleted.
|
||||||
DeleteChannepMapping(req DeleteChannelMappingRequest) error
|
DeleteChannepMapping(req DeleteChannelMappingRequest) error
|
||||||
|
|
|
@ -15,11 +15,11 @@ const (
|
||||||
// BridgeMessage represents a message that can be passed between any bridge types
|
// BridgeMessage represents a message that can be passed between any bridge types
|
||||||
type BridgeMessage struct {
|
type BridgeMessage struct {
|
||||||
// Source information
|
// Source information
|
||||||
SourceBridge string // "xmpp", "mattermost", "slack", etc.
|
SourceBridge string // "xmpp", "mattermost", "slack", etc.
|
||||||
SourceChannelID string // Channel ID in source system
|
SourceChannelID string // Channel ID in source system
|
||||||
SourceUserID string // User ID in source system (JID, user ID, etc.)
|
SourceUserID string // User ID in source system (JID, user ID, etc.)
|
||||||
SourceUserName string // Display name in source system
|
SourceUserName string // Display name in source system
|
||||||
SourceRemoteID string // Remote ID of the bridge instance that created this message
|
SourceRemoteID string // Remote ID of the bridge instance that created this message
|
||||||
|
|
||||||
// Message content (standardized on Markdown)
|
// Message content (standardized on Markdown)
|
||||||
Content string // Markdown formatted message content
|
Content string // Markdown formatted message content
|
||||||
|
|
|
@ -22,12 +22,12 @@ func SanitizeShareName(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure it starts with alphanumeric
|
// Ensure it starts with alphanumeric
|
||||||
for len(result) > 0 && (result[0] == '-' || result[0] == '_') {
|
for result != "" && (result[0] == '-' || result[0] == '_') {
|
||||||
result = result[1:]
|
result = result[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure it ends with alphanumeric
|
// Ensure it ends with alphanumeric
|
||||||
for len(result) > 0 && (result[len(result)-1] == '-' || result[len(result)-1] == '_') {
|
for result != "" && (result[len(result)-1] == '-' || result[len(result)-1] == '_') {
|
||||||
result = result[:len(result)-1]
|
result = result[:len(result)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,12 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
"github.com/mattermost/mattermost/server/public/pluginapi"
|
||||||
|
"github.com/mattermost/mattermost/server/public/pluginapi/cluster"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
|
||||||
mattermostbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/mattermost"
|
mattermostbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/mattermost"
|
||||||
xmppbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/xmpp"
|
xmppbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/xmpp"
|
||||||
|
@ -14,11 +20,6 @@ import (
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
||||||
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
|
||||||
"github.com/mattermost/mattermost/server/public/pluginapi"
|
|
||||||
"github.com/mattermost/mattermost/server/public/pluginapi/cluster"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
||||||
|
@ -78,7 +79,7 @@ func (p *Plugin) OnActivate() error {
|
||||||
p.bridgeManager = bridge.NewBridgeManager(p.logger, p.API, p.remoteID)
|
p.bridgeManager = bridge.NewBridgeManager(p.logger, p.API, p.remoteID)
|
||||||
|
|
||||||
// Initialize and register bridges with current configuration
|
// Initialize and register bridges with current configuration
|
||||||
if err := p.initBridges(*cfg); err != nil {
|
if err := p.initBridges(cfg); err != nil {
|
||||||
return fmt.Errorf("failed to initialize bridges: %w", err)
|
return fmt.Errorf("failed to initialize bridges: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,13 +142,13 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) initBridges(cfg config.Configuration) error {
|
func (p *Plugin) initBridges(cfg *config.Configuration) error {
|
||||||
// Create and register XMPP bridge
|
// Create and register XMPP bridge
|
||||||
xmppBridge := xmppbridge.NewBridge(
|
xmppBridge := xmppbridge.NewBridge(
|
||||||
p.logger,
|
p.logger,
|
||||||
p.API,
|
p.API,
|
||||||
p.kvstore,
|
p.kvstore,
|
||||||
&cfg,
|
cfg,
|
||||||
"xmpp",
|
"xmpp",
|
||||||
p.remoteID,
|
p.remoteID,
|
||||||
)
|
)
|
||||||
|
@ -161,7 +162,7 @@ func (p *Plugin) initBridges(cfg config.Configuration) error {
|
||||||
p.logger,
|
p.logger,
|
||||||
p.API,
|
p.API,
|
||||||
p.kvstore,
|
p.kvstore,
|
||||||
&cfg,
|
cfg,
|
||||||
p.botUserID,
|
p.botUserID,
|
||||||
"mattermost",
|
"mattermost",
|
||||||
"mattermost",
|
"mattermost",
|
||||||
|
|
|
@ -10,20 +10,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServeHTTP(t *testing.T) {
|
func TestServeHTTP(t *testing.T) {
|
||||||
assert := assert.New(t)
|
a := assert.New(t)
|
||||||
plugin := Plugin{}
|
plugin := Plugin{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r := httptest.NewRequest(http.MethodGet, "/api/v1/hello", nil)
|
r := httptest.NewRequest(http.MethodGet, "/api/v1/hello", http.NoBody)
|
||||||
r.Header.Set("Mattermost-User-ID", "test-user-id")
|
r.Header.Set("Mattermost-User-ID", "test-user-id")
|
||||||
|
|
||||||
plugin.ServeHTTP(nil, w, r)
|
plugin.ServeHTTP(nil, w, r)
|
||||||
|
|
||||||
result := w.Result()
|
result := w.Result()
|
||||||
assert.NotNil(result)
|
a.NotNil(result)
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
bodyBytes, err := io.ReadAll(result.Body)
|
bodyBytes, err := io.ReadAll(result.Body)
|
||||||
assert.Nil(err)
|
a.Nil(err)
|
||||||
bodyString := string(bodyBytes)
|
bodyString := string(bodyBytes)
|
||||||
|
|
||||||
assert.Equal("Hello, world!", bodyString)
|
a.Equal("Hello, world!", bodyString)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jellydator/ttlcache/v3"
|
"github.com/jellydator/ttlcache/v3"
|
||||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
|
||||||
"mellium.im/sasl"
|
"mellium.im/sasl"
|
||||||
"mellium.im/xmlstream"
|
"mellium.im/xmlstream"
|
||||||
"mellium.im/xmpp"
|
"mellium.im/xmpp"
|
||||||
|
@ -20,6 +19,8 @@ import (
|
||||||
"mellium.im/xmpp/muc"
|
"mellium.im/xmpp/muc"
|
||||||
"mellium.im/xmpp/mux"
|
"mellium.im/xmpp/mux"
|
||||||
"mellium.im/xmpp/stanza"
|
"mellium.im/xmpp/stanza"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -52,7 +53,7 @@ type Client struct {
|
||||||
sessionServing bool
|
sessionServing bool
|
||||||
|
|
||||||
// Message handling for bridge integration
|
// Message handling for bridge integration
|
||||||
messageHandler mux.MessageHandlerFunc // Bridge handler for incoming messages
|
messageHandler mux.MessageHandlerFunc // Bridge handler for incoming messages
|
||||||
|
|
||||||
// Message deduplication cache to handle XMPP server duplicates
|
// Message deduplication cache to handle XMPP server duplicates
|
||||||
dedupeCache *ttlcache.Cache[string, time.Time]
|
dedupeCache *ttlcache.Cache[string, time.Time]
|
||||||
|
@ -80,6 +81,8 @@ type MessageBody struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// XMPPMessage represents a complete XMPP message stanza
|
// XMPPMessage represents a complete XMPP message stanza
|
||||||
|
//
|
||||||
|
//nolint:revive // XMPPMessage is clearer than Message in this context
|
||||||
type XMPPMessage struct {
|
type XMPPMessage struct {
|
||||||
XMLName xml.Name `xml:"jabber:client message"`
|
XMLName xml.Name `xml:"jabber:client message"`
|
||||||
Type string `xml:"type,attr"`
|
Type string `xml:"type,attr"`
|
||||||
|
@ -91,7 +94,7 @@ type XMPPMessage struct {
|
||||||
// MessageWithBody represents a message stanza with body for parsing
|
// MessageWithBody represents a message stanza with body for parsing
|
||||||
type MessageWithBody struct {
|
type MessageWithBody struct {
|
||||||
stanza.Message
|
stanza.Message
|
||||||
Body string `xml:"body"`
|
Body string `xml:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GhostUser represents an XMPP ghost user
|
// GhostUser represents an XMPP ghost user
|
||||||
|
@ -107,7 +110,7 @@ type UserProfile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new XMPP client.
|
// NewClient creates a new XMPP client.
|
||||||
func NewClient(serverURL, username, password, resource, remoteID string, logger logger.Logger) *Client {
|
func NewClient(serverURL, username, password, resource, remoteID string, log logger.Logger) *Client {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Create TTL cache for message deduplication
|
// Create TTL cache for message deduplication
|
||||||
|
@ -119,16 +122,16 @@ func NewClient(serverURL, username, password, resource, remoteID string, logger
|
||||||
go dedupeCache.Start()
|
go dedupeCache.Start()
|
||||||
|
|
||||||
client := &Client{
|
client := &Client{
|
||||||
serverURL: serverURL,
|
serverURL: serverURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
resource: resource,
|
resource: resource,
|
||||||
remoteID: remoteID,
|
remoteID: remoteID,
|
||||||
logger: logger,
|
logger: log,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
sessionReady: make(chan struct{}),
|
sessionReady: make(chan struct{}),
|
||||||
dedupeCache: dedupeCache,
|
dedupeCache: dedupeCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create MUC client and set up message handling
|
// Create MUC client and set up message handling
|
||||||
|
@ -136,17 +139,17 @@ func NewClient(serverURL, username, password, resource, remoteID string, logger
|
||||||
client.mucClient = mucClient
|
client.mucClient = mucClient
|
||||||
|
|
||||||
// Create mux with MUC client and our message handler
|
// Create mux with MUC client and our message handler
|
||||||
mux := mux.New("jabber:client",
|
messageMux := mux.New("jabber:client",
|
||||||
muc.HandleClient(mucClient),
|
muc.HandleClient(mucClient),
|
||||||
mux.MessageFunc(stanza.GroupChatMessage, xml.Name{}, client.handleIncomingMessage))
|
mux.MessageFunc(stanza.GroupChatMessage, xml.Name{}, client.handleIncomingMessage))
|
||||||
client.mux = mux
|
client.mux = messageMux
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithTLS creates a new XMPP client with custom TLS configuration.
|
// NewClientWithTLS creates a new XMPP client with custom TLS configuration.
|
||||||
func NewClientWithTLS(serverURL, username, password, resource, remoteID string, tlsConfig *tls.Config, logger logger.Logger) *Client {
|
func NewClientWithTLS(serverURL, username, password, resource, remoteID string, tlsConfig *tls.Config, log logger.Logger) *Client {
|
||||||
client := NewClient(serverURL, username, password, resource, remoteID, logger)
|
client := NewClient(serverURL, username, password, resource, remoteID, log)
|
||||||
client.tlsConfig = tlsConfig
|
client.tlsConfig = tlsConfig
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
@ -234,7 +237,7 @@ func (c *Client) Connect() error {
|
||||||
if c.tlsConfig != nil {
|
if c.tlsConfig != nil {
|
||||||
tlsConfig = c.tlsConfig
|
tlsConfig = c.tlsConfig
|
||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{ //nolint:gosec // Default TLS config without MinVersion for XMPP compatibility
|
||||||
ServerName: c.jidAddr.Domain().String(),
|
ServerName: c.jidAddr.Domain().String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -372,15 +375,15 @@ func (c *Client) ExtractChannelID(from jid.JID) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractUserInfo extracts user ID and display name from a message JID
|
// ExtractUserInfo extracts user ID and display name from a message JID
|
||||||
func (c *Client) ExtractUserInfo(from jid.JID) (string, string) {
|
func (c *Client) ExtractUserInfo(from jid.JID) (userID, displayName string) {
|
||||||
// For MUC messages, the resource part is the nickname
|
// For MUC messages, the resource part is the nickname
|
||||||
nickname := from.Resourcepart()
|
nickname := from.Resourcepart()
|
||||||
|
|
||||||
// Use the full JID as user ID for XMPP
|
// Use the full JID as user ID for XMPP
|
||||||
userID := from.String()
|
userID = from.String()
|
||||||
|
|
||||||
// Use nickname as display name if available, otherwise use full JID
|
// Use nickname as display name if available, otherwise use full JID
|
||||||
displayName := nickname
|
displayName = nickname
|
||||||
if displayName == "" {
|
if displayName == "" {
|
||||||
displayName = from.String()
|
displayName = from.String()
|
||||||
}
|
}
|
||||||
|
@ -486,7 +489,7 @@ func (c *Client) LeaveRoom(roomJID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage sends a message to an XMPP room
|
// SendMessage sends a message to an XMPP room
|
||||||
func (c *Client) SendMessage(req MessageRequest) (*SendMessageResponse, error) {
|
func (c *Client) SendMessage(req *MessageRequest) (*SendMessageResponse, error) {
|
||||||
if c.session == nil {
|
if c.session == nil {
|
||||||
if err := c.Connect(); err != nil {
|
if err := c.Connect(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -736,8 +739,9 @@ func (c *Client) Ping() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// handleIncomingMessage processes incoming XMPP message stanzas
|
// handleIncomingMessage processes incoming XMPP message stanzas
|
||||||
|
//
|
||||||
|
//nolint:gocritic // msg parameter must match external XMPP library handler signature
|
||||||
func (c *Client) handleIncomingMessage(msg stanza.Message, t xmlstream.TokenReadEncoder) error {
|
func (c *Client) handleIncomingMessage(msg stanza.Message, t xmlstream.TokenReadEncoder) error {
|
||||||
c.logger.LogDebug("Received XMPP message",
|
c.logger.LogDebug("Received XMPP message",
|
||||||
"from", msg.From.String(),
|
"from", msg.From.String(),
|
||||||
|
@ -770,4 +774,3 @@ func (c *Client) handleIncomingMessage(msg stanza.Message, t xmlstream.TokenRead
|
||||||
c.logger.LogDebug("No message handler set, ignoring message")
|
c.logger.LogDebug("No message handler set, ignoring message")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue