diff --git a/plugin.json b/plugin.json index 4d7660dc..13e34810 100644 --- a/plugin.json +++ b/plugin.json @@ -2,8 +2,8 @@ "id": "jitsi", "name": "Jitsi", "description": "Jitsi audio and video conferencing plugin for Mattermost. Follow https://github.com/seansackowitz/mattermost-plugin-jitsi for notifications on updates.", - "version": "1.0.0", - "backend": { + "version": "1.1.0", + "server": { "executable": "server/plugin.exe" }, "webapp": { diff --git a/server/configuration.go b/server/configuration.go new file mode 100644 index 00000000..d2bbfc60 --- /dev/null +++ b/server/configuration.go @@ -0,0 +1,97 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package main + +import ( + "fmt" + "reflect" + + "github.com/pkg/errors" +) + +// 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 { + JitsiURL string +} + +// 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 +} + +// IsValid checks if all needed fields are set. +func (c *configuration) IsValid() error { + if len(c.JitsiURL) == 0 { + return fmt.Errorf("JitsiUrl is not configured.") + } + + 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 { + p.configurationLock.RLock() + defer p.configurationLock.RUnlock() + + if p.configuration == nil { + return &configuration{} + } + + return p.configuration +} + +// setConfiguration replaces the active configuration under lock. +// +// Do not call setConfiguration while holding the configurationLock, as sync.Mutex is not +// reentrant. In particular, avoid using the plugin API entirely, as this may in turn trigger a +// hook back into the plugin. If that hook attempts to acquire this lock, a deadlock may occur. +// +// 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) { + p.configurationLock.Lock() + defer p.configurationLock.Unlock() + + if configuration != nil && p.configuration == configuration { + // Ignore assignment if the configuration struct is empty. Go will optimize the + // allocation for same to point at the same memory address, breaking the check + // above. + if reflect.ValueOf(*configuration).NumField() == 0 { + return + } + + panic("setConfiguration called with the existing configuration") + } + + p.configuration = configuration +} + +// OnConfigurationChange is invoked when configuration changes may have been made. +func (p *Plugin) OnConfigurationChange() error { + var configuration = new(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.setConfiguration(configuration) + + return nil +} \ No newline at end of file diff --git a/server/plugin.go b/server/plugin.go index b85d709a..1d4418a8 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -7,6 +7,7 @@ import ( "net/http" "regexp" "strings" + "sync" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/plugin" @@ -19,29 +20,20 @@ const ( type Plugin struct { plugin.MattermostPlugin - JitsiURL string -} - -// func (p *Plugin) OnActivate() error { -// if err := p.IsConfigurationValid(); err != nil { -// return err -// } - -// return nil -// } + // configurationLock synchronizes access to the configuration. + configurationLock sync.RWMutex -// func (p *Plugin) OnConfigurationChange() error { -// if err := p.IsConfigurationValid(); err != nil { -// return err -// } - -// return nil -// } + // configuration is the active plugin configuration. Consult getConfiguration and + // setConfiguration for usage. + configuration *configuration +} -func (p *Plugin) IsConfigurationValid() error { - if len(p.JitsiURL) == 0 { - return fmt.Errorf("Jitsi URL is not configured") +func (p *Plugin) OnActivate() error { + config := p.getConfiguration() + if err := config.IsValid(); err != nil { + return err } + return nil } @@ -70,7 +62,7 @@ func encodeJitsiMeetingID(meeting string) string { } func (p *Plugin) handleStartMeeting(w http.ResponseWriter, r *http.Request) { - if err := p.IsConfigurationValid(); err != nil { + if err := p.getConfiguration().IsValid(); err != nil { http.Error(w, err.Error(), http.StatusTeapot) return } @@ -105,7 +97,7 @@ func (p *Plugin) handleStartMeeting(w http.ResponseWriter, r *http.Request) { if len(req.Topic) < 1 { meetingID = generateRoomWithoutSeparator() } - jitsiURL := strings.TrimSpace(p.JitsiURL) + jitsiURL := strings.TrimSpace(p.getConfiguration().JitsiURL) meetingURL := jitsiURL + "/" + meetingID post := &model.Post{ diff --git a/webapp/package.json b/webapp/package.json index 7c96129d..dc6c9158 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "jitsi", - "version": "1.0.0", + "version": "1.1.0", "description": "Jitsi audio and video conferencing plugin for Mattermost", "dependencies": { "mattermost-redux": "1.0.1",