chore: fix lint issues

This commit is contained in:
Felipe M 2025-08-06 18:25:25 +02:00
parent 17ea21a579
commit 7c37953c28
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
20 changed files with 136 additions and 131 deletions

View file

@ -124,7 +124,7 @@ func testXMPPClient(config *Config) error {
log.Printf("Using insecure TLS configuration (skipping certificate verification)")
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
InsecureSkipVerify: true, //nolint:gosec // This is a testing tool for development environments
}
client = xmpp.NewClientWithTLS(
config.Server,
@ -302,7 +302,7 @@ func testMUCOperations(client *xmpp.Client, config *Config) error {
}
start = time.Now()
_, err = client.SendMessage(messageReq)
_, err = client.SendMessage(&messageReq)
if err != nil {
return fmt.Errorf("failed to send test message to room %s: %w", config.TestRoom, err)
}

View file

@ -5,14 +5,17 @@ import (
"fmt"
"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/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
//
//nolint:revive // BridgeManager is clearer than Manager in this context
type BridgeManager struct {
bridges map[string]model.Bridge
mu sync.RWMutex
@ -26,22 +29,15 @@ type BridgeManager struct {
}
// 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")
}
func NewBridgeManager(log logger.Logger, api plugin.API, remoteID string) model.BridgeManager {
ctx, cancel := context.WithCancel(context.Background())
return &BridgeManager{
bridges: make(map[string]model.Bridge),
logger: logger,
logger: log,
api: api,
remoteID: remoteID,
messageBus: NewMessageBus(logger),
messageBus: NewMessageBus(log),
routingCtx: ctx,
routingCancel: cancel,
}
@ -229,7 +225,7 @@ func (m *BridgeManager) Shutdown() error {
}
// 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()
defer m.mu.RUnlock()
@ -241,7 +237,7 @@ func (m *BridgeManager) OnPluginConfigurationChange(config *config.Configuration
var errors []error
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))
m.logger.LogError("Failed to update bridge configuration", "bridge_id", name, "error", err)
} 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
func (m *BridgeManager) CreateChannelMapping(req model.CreateChannelMappingRequest) error {
func (m *BridgeManager) CreateChannelMapping(req *model.CreateChannelMappingRequest) error {
// Validate request
if err := req.Validate(); err != nil {
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
func (m *BridgeManager) shareChannel(req model.CreateChannelMappingRequest) error {
func (m *BridgeManager) shareChannel(req *model.CreateChannelMappingRequest) error {
if m.remoteID == "" {
return fmt.Errorf("remote ID not set - plugin not registered for shared channels")
}

View file

@ -6,12 +6,13 @@ import (
"sync"
"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/config"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
"github.com/mattermost/mattermost/server/public/plugin"
)
const (
@ -49,12 +50,12 @@ type mattermostBridge struct {
}
// 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())
b := &mattermostBridge{
logger: log,
api: api,
kvstore: kvstore,
kvstore: store,
botUserID: botUserID,
bridgeID: bridgeID,
remoteID: remoteID,
@ -73,13 +74,6 @@ func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *
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
func (b *mattermostBridge) UpdateConfiguration(cfg *config.Configuration) error {
// Validate configuration using built-in validation
@ -102,10 +96,10 @@ func (b *mattermostBridge) Start() error {
b.logger.LogDebug("Starting Mattermost bridge")
b.configMu.RLock()
config := b.config
cfg := b.config
b.configMu.RUnlock()
if config == nil {
if cfg == nil {
return fmt.Errorf("bridge configuration not set")
}
@ -207,11 +201,11 @@ func (b *mattermostBridge) IsConnected() bool {
// Ping actively tests the Mattermost API connectivity
func (b *mattermostBridge) Ping() error {
if !b.connected.Load() {
return fmt.Errorf("Mattermost bridge is not connected")
return fmt.Errorf("mattermost bridge is not connected")
}
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")
@ -221,7 +215,7 @@ func (b *mattermostBridge) Ping() error {
version := b.api.GetServerVersion()
if 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)
@ -313,7 +307,7 @@ func (b *mattermostBridge) DeleteChannelMapping(channelID string) error {
// ChannelMappingExists checks if a Mattermost channel exists on the server
func (b *mattermostBridge) ChannelMappingExists(roomID string) (bool, error) {
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)

View file

@ -4,9 +4,10 @@ import (
"fmt"
"strings"
mmModel "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
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
@ -59,7 +60,7 @@ func (h *mattermostMessageHandler) GetSupportedMessageTypes() []string {
// postMessageToMattermost posts a message to a Mattermost channel
func (h *mattermostMessageHandler) postMessageToMattermost(msg *pluginModel.BridgeMessage) error {
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
@ -243,7 +244,7 @@ func (r *mattermostUserResolver) ResolveUser(externalUserID string) (*pluginMode
}
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{

View file

@ -6,14 +6,17 @@ import (
"sync"
"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/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"
)
// MattermostUser represents a Mattermost user that implements the BridgeUser interface
//
//nolint:revive // MattermostUser is clearer than User in this context
type MattermostUser struct {
// User identity
id string
@ -40,7 +43,7 @@ type MattermostUser struct {
}
// 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())
return &MattermostUser{
@ -53,7 +56,7 @@ func NewMattermostUser(id, displayName, username, email string, api plugin.API,
config: cfg,
ctx: ctx,
cancel: cancel,
logger: logger,
logger: log,
}
}
@ -69,7 +72,7 @@ func (u *MattermostUser) Validate() error {
return fmt.Errorf("configuration cannot be nil")
}
if u.api == nil {
return fmt.Errorf("Mattermost API cannot be nil")
return fmt.Errorf("mattermost API cannot be nil")
}
return nil
}
@ -192,13 +195,13 @@ func (u *MattermostUser) IsConnected() bool {
func (u *MattermostUser) Ping() error {
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
version := u.api.GetServerVersion()
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
@ -207,7 +210,7 @@ func (u *MattermostUser) Ping() error {
// CheckChannelExists checks if a Mattermost channel exists
func (u *MattermostUser) CheckChannelExists(channelID string) (bool, error) {
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

View file

@ -35,7 +35,7 @@ type messageBus struct {
}
// 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())
return &messageBus{
@ -43,7 +43,7 @@ func NewMessageBus(logger logger.Logger) model.MessageBus {
subscribers: make(map[string]chan *model.DirectionalMessage),
ctx: ctx,
cancel: cancel,
logger: logger,
logger: log,
}
}

View file

@ -21,11 +21,11 @@ type UserManager struct {
}
// 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())
return &UserManager{
bridgeType: bridgeType,
logger: logger,
logger: log,
users: make(map[string]model.BridgeUser),
ctx: ctx,
cancel: cancel,

View file

@ -9,15 +9,16 @@ import (
"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/config"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
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 (
@ -55,12 +56,12 @@ type xmppBridge struct {
}
// 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())
b := &xmppBridge{
logger: log,
api: api,
kvstore: kvstore,
kvstore: store,
ctx: ctx,
cancel: cancel,
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 {
// Create TLS config based on certificate verification setting
tlsConfig := &tls.Config{
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify,
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify, //nolint:gosec // Allow insecure TLS for testing environments
}
return xmppClient.NewClientWithTLS(
@ -154,19 +155,19 @@ func (b *xmppBridge) Start() error {
b.logger.LogDebug("Starting Mattermost to XMPP bridge")
b.configMu.RLock()
config := b.config
cfg := b.config
b.configMu.RUnlock()
if config == nil {
if cfg == nil {
return fmt.Errorf("bridge configuration not set")
}
if !config.EnableSync {
if !cfg.EnableSync {
b.logger.LogInfo("XMPP sync is disabled, bridge will not start")
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
if err := b.connectToXMPP(); err != nil {
@ -340,10 +341,10 @@ func (b *xmppBridge) connectionMonitor() {
// handleReconnection attempts to reconnect to XMPP and rejoin rooms
func (b *xmppBridge) handleReconnection() {
b.configMu.RLock()
config := b.config
cfg := b.config
b.configMu.RUnlock()
if config == nil || !config.EnableSync {
if cfg == nil || !cfg.EnableSync {
return
}
@ -357,7 +358,7 @@ func (b *xmppBridge) handleReconnection() {
// Retry connection with exponential backoff
maxRetries := 3
for i := range maxRetries {
backoff := time.Duration(1<<uint(i)) * time.Second
backoff := time.Duration(1<<i) * time.Second
select {
case <-b.ctx.Done():
@ -604,6 +605,8 @@ func (b *xmppBridge) ID() string {
}
// 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 {
b.logger.LogDebug("XMPP bridge handling incoming message",
"from", msg.From.String(),

View file

@ -85,7 +85,7 @@ func (h *xmppMessageHandler) sendMessageToXMPP(msg *pluginModel.BridgeMessage) e
}
// Send the message
_, err = h.bridge.bridgeClient.SendMessage(req)
_, err = h.bridge.bridgeClient.SendMessage(&req)
if err != nil {
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 {
// For XMPP JIDs, extract the local part or resource as display name
// Format: user@domain/resource -> use resource or user
if len(externalUserID) == 0 {
if externalUserID == "" {
return "Unknown User"
}

View file

@ -15,6 +15,8 @@ import (
)
// XMPPUser represents an XMPP user that implements the BridgeUser interface
//
//nolint:revive // XMPPUser is clearer than User in this context
type XMPPUser struct {
// User identity
id string
@ -41,12 +43,12 @@ type XMPPUser struct {
}
// 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())
// Create TLS config based on certificate verification setting
tlsConfig := &tls.Config{
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify,
InsecureSkipVerify: cfg.XMPPInsecureSkipVerify, //nolint:gosec // Allow insecure TLS for testing environments
}
// Create XMPP client for this user
@ -57,7 +59,7 @@ func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, logger
cfg.GetXMPPResource(),
id, // Use user ID as remote ID
tlsConfig,
logger,
log,
)
return &XMPPUser{
@ -69,7 +71,7 @@ func NewXMPPUser(id, displayName, jid string, cfg *config.Configuration, logger
config: cfg,
ctx: ctx,
cancel: cancel,
logger: logger,
logger: log,
}
}
@ -171,7 +173,7 @@ func (u *XMPPUser) SendMessageToChannel(channelID, message string) error {
Message: message,
}
_, err := u.client.SendMessage(req)
_, err := u.client.SendMessage(&req)
if err != nil {
return fmt.Errorf("failed to send message to XMPP room %s: %w", channelID, err)
}

View file

@ -4,10 +4,11 @@ import (
"fmt"
"strings"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/pluginapi"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
)
type Handler struct {
@ -183,7 +184,7 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
TeamID: args.TeamId,
}
err = c.bridgeManager.CreateChannelMapping(mappingReq)
err = c.bridgeManager.CreateChannelMapping(&mappingReq)
if err != nil {
return c.formatMappingError("create", roomJID, err)
}
@ -265,11 +266,12 @@ func (c *Handler) executeStatusCommand(args *model.CommandArgs) *model.CommandRe
roomJID, err := bridge.GetChannelMapping(channelID)
var mappingText string
if err != nil {
switch {
case err != nil:
mappingText = fmt.Sprintf("⚠️ Error checking channel mapping: %v", err)
} else if roomJID != "" {
case roomJID != "":
mappingText = fmt.Sprintf("🔗 **Current channel mapping:** `%s`", roomJID)
} else {
default:
mappingText = "📝 **Current channel:** Not mapped to any XMPP room"
}

View file

@ -3,8 +3,9 @@ package main
import (
"reflect"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
"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

View file

@ -4,8 +4,9 @@ import (
"fmt"
"time"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/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
@ -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
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()
@ -65,11 +69,6 @@ func (p *Plugin) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteClu
ReactionsLastUpdateAt: now,
}
var remoteClusterID string
if rc != nil {
remoteClusterID = rc.RemoteId
}
p.logger.LogDebug("OnSharedChannelsSyncMsg called",
"remote_cluster_id", remoteClusterID,
"channel_id", msg.ChannelId,

View file

@ -28,7 +28,7 @@ type CreateChannelMappingRequest struct {
}
// Validate checks if all required fields are present and valid
func (r CreateChannelMappingRequest) Validate() error {
func (r *CreateChannelMappingRequest) Validate() error {
if r.ChannelID == "" {
return fmt.Errorf("channelID cannot be empty")
}
@ -116,7 +116,7 @@ type BridgeManager interface {
OnPluginConfigurationChange(config *config.Configuration) error
// 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(req DeleteChannelMappingRequest) error

View file

@ -15,11 +15,11 @@ const (
// BridgeMessage represents a message that can be passed between any bridge types
type BridgeMessage struct {
// Source information
SourceBridge string // "xmpp", "mattermost", "slack", etc.
SourceChannelID string // Channel ID in source system
SourceUserID string // User ID in source system (JID, user ID, etc.)
SourceUserName string // Display name in source system
SourceRemoteID string // Remote ID of the bridge instance that created this message
SourceBridge string // "xmpp", "mattermost", "slack", etc.
SourceChannelID string // Channel ID in source system
SourceUserID string // User ID in source system (JID, user ID, etc.)
SourceUserName string // Display name in source system
SourceRemoteID string // Remote ID of the bridge instance that created this message
// Message content (standardized on Markdown)
Content string // Markdown formatted message content

View file

@ -22,12 +22,12 @@ func SanitizeShareName(name string) string {
}
// Ensure it starts with alphanumeric
for len(result) > 0 && (result[0] == '-' || result[0] == '_') {
for result != "" && (result[0] == '-' || result[0] == '_') {
result = result[1:]
}
// 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]
}

View file

@ -6,6 +6,12 @@ import (
"sync"
"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"
mattermostbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/mattermost"
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"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"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.
@ -78,7 +79,7 @@ func (p *Plugin) OnActivate() error {
p.bridgeManager = bridge.NewBridgeManager(p.logger, p.API, p.remoteID)
// 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)
}
@ -141,13 +142,13 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
return response, nil
}
func (p *Plugin) initBridges(cfg config.Configuration) error {
func (p *Plugin) initBridges(cfg *config.Configuration) error {
// Create and register XMPP bridge
xmppBridge := xmppbridge.NewBridge(
p.logger,
p.API,
p.kvstore,
&cfg,
cfg,
"xmpp",
p.remoteID,
)
@ -161,7 +162,7 @@ func (p *Plugin) initBridges(cfg config.Configuration) error {
p.logger,
p.API,
p.kvstore,
&cfg,
cfg,
p.botUserID,
"mattermost",
"mattermost",

View file

@ -10,20 +10,20 @@ import (
)
func TestServeHTTP(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
plugin := Plugin{}
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")
plugin.ServeHTTP(nil, w, r)
result := w.Result()
assert.NotNil(result)
a.NotNil(result)
defer result.Body.Close()
bodyBytes, err := io.ReadAll(result.Body)
assert.Nil(err)
a.Nil(err)
bodyString := string(bodyBytes)
assert.Equal("Hello, world!", bodyString)
a.Equal("Hello, world!", bodyString)
}

View file

@ -28,4 +28,4 @@ func ExtractIdentifierFromChannelMapKey(key, bridgeName string) string {
return ""
}
return key[len(expectedPrefix):]
}
}

View file

@ -11,7 +11,6 @@ import (
"time"
"github.com/jellydator/ttlcache/v3"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
"mellium.im/sasl"
"mellium.im/xmlstream"
"mellium.im/xmpp"
@ -20,6 +19,8 @@ import (
"mellium.im/xmpp/muc"
"mellium.im/xmpp/mux"
"mellium.im/xmpp/stanza"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
)
const (
@ -52,7 +53,7 @@ type Client struct {
sessionServing bool
// 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
dedupeCache *ttlcache.Cache[string, time.Time]
@ -80,6 +81,8 @@ type MessageBody struct {
}
// XMPPMessage represents a complete XMPP message stanza
//
//nolint:revive // XMPPMessage is clearer than Message in this context
type XMPPMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
Type string `xml:"type,attr"`
@ -91,7 +94,7 @@ type XMPPMessage struct {
// MessageWithBody represents a message stanza with body for parsing
type MessageWithBody struct {
stanza.Message
Body string `xml:"body"`
Body string `xml:"body"`
}
// GhostUser represents an XMPP ghost user
@ -107,7 +110,7 @@ type UserProfile struct {
}
// 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())
// Create TTL cache for message deduplication
@ -119,16 +122,16 @@ func NewClient(serverURL, username, password, resource, remoteID string, logger
go dedupeCache.Start()
client := &Client{
serverURL: serverURL,
username: username,
password: password,
resource: resource,
remoteID: remoteID,
logger: logger,
ctx: ctx,
cancel: cancel,
sessionReady: make(chan struct{}),
dedupeCache: dedupeCache,
serverURL: serverURL,
username: username,
password: password,
resource: resource,
remoteID: remoteID,
logger: log,
ctx: ctx,
cancel: cancel,
sessionReady: make(chan struct{}),
dedupeCache: dedupeCache,
}
// Create MUC client and set up message handling
@ -136,17 +139,17 @@ func NewClient(serverURL, username, password, resource, remoteID string, logger
client.mucClient = mucClient
// Create mux with MUC client and our message handler
mux := mux.New("jabber:client",
messageMux := mux.New("jabber:client",
muc.HandleClient(mucClient),
mux.MessageFunc(stanza.GroupChatMessage, xml.Name{}, client.handleIncomingMessage))
client.mux = mux
client.mux = messageMux
return client
}
// 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 {
client := NewClient(serverURL, username, password, resource, remoteID, logger)
func NewClientWithTLS(serverURL, username, password, resource, remoteID string, tlsConfig *tls.Config, log logger.Logger) *Client {
client := NewClient(serverURL, username, password, resource, remoteID, log)
client.tlsConfig = tlsConfig
return client
}
@ -234,7 +237,7 @@ func (c *Client) Connect() error {
if c.tlsConfig != nil {
tlsConfig = c.tlsConfig
} else {
tlsConfig = &tls.Config{
tlsConfig = &tls.Config{ //nolint:gosec // Default TLS config without MinVersion for XMPP compatibility
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
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
nickname := from.Resourcepart()
// 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
displayName := nickname
displayName = nickname
if displayName == "" {
displayName = from.String()
}
@ -486,7 +489,7 @@ func (c *Client) LeaveRoom(roomJID string) error {
}
// 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 err := c.Connect(); err != nil {
return nil, err
@ -736,8 +739,9 @@ func (c *Client) Ping() error {
return nil
}
// 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 {
c.logger.LogDebug("Received XMPP message",
"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")
return nil
}