feat: complete XMPP bridge implementation with configuration fixes
- Fix configuration loading by matching JSON field names with plugin manifest keys - Move configuration to separate package to resolve type conflicts - Implement bridge startup logic that initializes on OnActivate and updates on OnConfigurationChange - Add certificate verification skip option for development/testing environments - Create XMPP client initialization helper function to avoid code duplication - Add SetOnlinePresence() method to XMPP client for presence management - Set bridge user online presence automatically upon successful XMPP connection - Remove unused mock generation and test files as requested - Update bridge constructor to accept configuration parameter - Implement proper bridge lifecycle management with Start/Stop methods The bridge now properly loads configuration from admin console, creates XMPP connections with appropriate TLS settings, and manages online presence for the bridge user. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
07ff46624d
commit
4d6929bab6
12 changed files with 801 additions and 242 deletions
|
@ -1,95 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const DefaultXMPPUsernamePrefix = "xmpp"
|
||||
|
||||
// configuration captures the plugin's external configuration as exposed in the Mattermost server
|
||||
// configuration, as well as values computed from the configuration. Any public fields will be
|
||||
// deserialized from the Mattermost server configuration in OnConfigurationChange.
|
||||
//
|
||||
// As plugins are inherently concurrent (hooks being called asynchronously), and the plugin
|
||||
// configuration can change at any time, access to the configuration must be synchronized. The
|
||||
// strategy used in this plugin is to guard a pointer to the configuration, and clone the entire
|
||||
// struct whenever it changes. You may replace this with whatever strategy you choose.
|
||||
//
|
||||
// If you add non-reference types to your configuration struct, be sure to rewrite Clone as a deep
|
||||
// copy appropriate for your types.
|
||||
type configuration struct {
|
||||
XMPPServerURL string `json:"xmpp_server_url"`
|
||||
XMPPUsername string `json:"xmpp_username"`
|
||||
XMPPPassword string `json:"xmpp_password"`
|
||||
EnableSync bool `json:"enable_sync"`
|
||||
XMPPUsernamePrefix string `json:"xmpp_username_prefix"`
|
||||
XMPPResource string `json:"xmpp_resource"`
|
||||
}
|
||||
|
||||
// Clone shallow copies the configuration. Your implementation may require a deep copy if
|
||||
// your configuration has reference types.
|
||||
func (c *configuration) Clone() *configuration {
|
||||
var clone = *c
|
||||
return &clone
|
||||
}
|
||||
|
||||
// GetXMPPUsernamePrefix returns the configured username prefix, or the default if not set
|
||||
func (c *configuration) GetXMPPUsernamePrefix() string {
|
||||
if c.XMPPUsernamePrefix == "" {
|
||||
return DefaultXMPPUsernamePrefix
|
||||
}
|
||||
return c.XMPPUsernamePrefix
|
||||
}
|
||||
|
||||
// GetXMPPResource returns the configured XMPP resource, or a default if not set
|
||||
func (c *configuration) GetXMPPResource() string {
|
||||
if c.XMPPResource == "" {
|
||||
return "mattermost-bridge"
|
||||
}
|
||||
return c.XMPPResource
|
||||
}
|
||||
|
||||
// IsValid validates the configuration and returns an error if invalid
|
||||
func (c *configuration) IsValid() error {
|
||||
if c.EnableSync {
|
||||
if c.XMPPServerURL == "" {
|
||||
return fmt.Errorf("XMPP Server URL is required when sync is enabled")
|
||||
}
|
||||
if c.XMPPUsername == "" {
|
||||
return fmt.Errorf("XMPP Username is required when sync is enabled")
|
||||
}
|
||||
if c.XMPPPassword == "" {
|
||||
return fmt.Errorf("XMPP Password is required when sync is enabled")
|
||||
}
|
||||
|
||||
// Validate server URL format
|
||||
if !strings.Contains(c.XMPPServerURL, ":") {
|
||||
return fmt.Errorf("XMPP Server URL must include port (e.g., server.com:5222)")
|
||||
}
|
||||
|
||||
// Validate username prefix doesn't contain invalid characters
|
||||
prefix := c.GetXMPPUsernamePrefix()
|
||||
if strings.ContainsAny(prefix, ":@/\\") {
|
||||
return fmt.Errorf("XMPP Username Prefix cannot contain special characters (:, @, /, \\)")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getConfiguration retrieves the active configuration under lock, making it safe to use
|
||||
// concurrently. The active configuration may change underneath the client of this method, but
|
||||
// the struct returned by this API call is considered immutable.
|
||||
func (p *Plugin) getConfiguration() *configuration {
|
||||
func (p *Plugin) getConfiguration() *config.Configuration {
|
||||
p.configurationLock.RLock()
|
||||
defer p.configurationLock.RUnlock()
|
||||
|
||||
if p.configuration == nil {
|
||||
return &configuration{}
|
||||
return &config.Configuration{}
|
||||
}
|
||||
|
||||
return p.configuration
|
||||
|
@ -104,7 +30,7 @@ func (p *Plugin) getConfiguration() *configuration {
|
|||
// This method panics if setConfiguration is called with the existing configuration. This almost
|
||||
// certainly means that the configuration was modified without being cloned and may result in
|
||||
// an unsafe access.
|
||||
func (p *Plugin) setConfiguration(configuration *configuration) {
|
||||
func (p *Plugin) setConfiguration(configuration *config.Configuration) {
|
||||
p.configurationLock.Lock()
|
||||
defer p.configurationLock.Unlock()
|
||||
|
||||
|
@ -124,19 +50,29 @@ func (p *Plugin) setConfiguration(configuration *configuration) {
|
|||
|
||||
// OnConfigurationChange is invoked when configuration changes may have been made.
|
||||
func (p *Plugin) OnConfigurationChange() error {
|
||||
var configuration = new(configuration)
|
||||
var configuration = new(config.Configuration)
|
||||
|
||||
// Load the public configuration fields from the Mattermost server configuration.
|
||||
if err := p.API.LoadPluginConfiguration(configuration); err != nil {
|
||||
return errors.Wrap(err, "failed to load plugin configuration")
|
||||
}
|
||||
|
||||
p.API.LogDebug("Loaded configuration in OnConfigurationChange", "configuration", configuration)
|
||||
|
||||
// Validate the configuration
|
||||
if err := configuration.IsValid(); err != nil {
|
||||
p.API.LogError("Configuration validation failed", "error", err, "configuration", configuration)
|
||||
return errors.Wrap(err, "invalid plugin configuration")
|
||||
}
|
||||
|
||||
p.setConfiguration(configuration)
|
||||
|
||||
// Update bridge configurations (only if bridges have been initialized)
|
||||
if p.mattermostToXMPPBridge != nil {
|
||||
if err := p.mattermostToXMPPBridge.UpdateConfiguration(configuration); err != nil {
|
||||
p.logger.LogWarn("Failed to update Mattermost to XMPP bridge configuration", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue