snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
399
server/channels/app/channels.go
Normal file
399
server/channels/app/channels.go
Normal file
@@ -0,0 +1,399 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/imaging"
|
||||
"github.com/mattermost/mattermost/server/v8/config"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/imageproxy"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
||||
)
|
||||
|
||||
type configService interface {
|
||||
Config() *model.Config
|
||||
AddConfigListener(listener func(*model.Config, *model.Config)) string
|
||||
RemoveConfigListener(id string)
|
||||
UpdateConfig(f func(*model.Config))
|
||||
SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError)
|
||||
}
|
||||
|
||||
// Channels contains all channels related state.
|
||||
type Channels struct {
|
||||
srv *Server
|
||||
cfgSvc configService
|
||||
filestore filestore.FileBackend
|
||||
exportFilestore filestore.FileBackend
|
||||
|
||||
postActionCookieSecret []byte
|
||||
samlRelayStateSigningKey []byte
|
||||
|
||||
pluginCommandsLock sync.RWMutex
|
||||
pluginCommands []*PluginCommand
|
||||
pluginsLock sync.RWMutex
|
||||
pluginsEnvironment *plugin.Environment
|
||||
pluginConfigListenerID string
|
||||
pluginLicenseListenerID string
|
||||
pluginClusterLeaderListenerID string
|
||||
|
||||
// guardCache caches ChannelGuards rows by ChannelId -> []*store.ChannelGuard.
|
||||
guardCache atomic.Pointer[sync.Map]
|
||||
|
||||
// guardCacheRetryInFlight collapses concurrent reload-failure retries to a single goroutine.
|
||||
// See scheduleGuardCacheReloadRetry.
|
||||
guardCacheRetryInFlight atomic.Bool
|
||||
|
||||
imageProxy *imageproxy.ImageProxy
|
||||
|
||||
agentsBridge AgentsBridge
|
||||
|
||||
// cached counts that are used during notice condition validation
|
||||
cachedPostCount int64
|
||||
cachedUserCount int64
|
||||
cachedDBMSVersion string
|
||||
// previously fetched notices
|
||||
cachedNotices model.ProductNotices
|
||||
|
||||
managedCategoryGroupID string
|
||||
managedCategoryFieldID string
|
||||
|
||||
AccountMigration einterfaces.AccountMigrationInterface
|
||||
Compliance einterfaces.ComplianceInterface
|
||||
DataRetention einterfaces.DataRetentionInterface
|
||||
MessageExport einterfaces.MessageExportInterface
|
||||
Saml einterfaces.SamlInterface
|
||||
Notification einterfaces.NotificationInterface
|
||||
Ldap einterfaces.LdapInterface
|
||||
AccessControl einterfaces.AccessControlServiceInterface
|
||||
Intune einterfaces.IntuneInterface
|
||||
|
||||
attributeViewRefreshMut sync.Mutex
|
||||
attributeViewRefreshLast time.Time
|
||||
attributeViewNeedsRefresh atomic.Bool
|
||||
|
||||
// These are used to prevent concurrent upload requests
|
||||
// for a given upload session which could cause inconsistencies
|
||||
// and data corruption.
|
||||
uploadLockMapMut sync.Mutex
|
||||
uploadLockMap map[string]bool
|
||||
|
||||
imgDecoder *imaging.Decoder
|
||||
imgEncoder *imaging.Encoder
|
||||
|
||||
dndTaskMut sync.Mutex
|
||||
dndTask *model.ScheduledTask
|
||||
|
||||
postReminderMut sync.Mutex
|
||||
postReminderTask *model.ScheduledTask
|
||||
|
||||
interruptQuitChan chan struct{}
|
||||
scheduledPostMut sync.Mutex
|
||||
scheduledPostTask *model.ScheduledTask
|
||||
}
|
||||
|
||||
func NewChannels(s *Server) (*Channels, error) {
|
||||
ch := &Channels{
|
||||
srv: s,
|
||||
imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()),
|
||||
uploadLockMap: map[string]bool{},
|
||||
filestore: s.FileBackend(),
|
||||
exportFilestore: s.ExportFileBackend(),
|
||||
cfgSvc: s.Platform(),
|
||||
interruptQuitChan: make(chan struct{}),
|
||||
}
|
||||
ch.guardCache.Store(&sync.Map{})
|
||||
|
||||
if s.agentsBridgeOverride != nil {
|
||||
ch.agentsBridge = s.agentsBridgeOverride
|
||||
} else {
|
||||
ch.agentsBridge = newLiveAgentsBridge(ch)
|
||||
}
|
||||
|
||||
// We are passing a partially filled Channels struct so that the enterprise
|
||||
// methods can have access to app methods.
|
||||
// Otherwise, passing server would mean it has to call s.Channels(),
|
||||
// which would be nil at this point.
|
||||
if complianceInterface != nil {
|
||||
ch.Compliance = complianceInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if messageExportInterface != nil {
|
||||
ch.MessageExport = messageExportInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if dataRetentionInterface != nil {
|
||||
ch.DataRetention = dataRetentionInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if accountMigrationInterface != nil {
|
||||
ch.AccountMigration = accountMigrationInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if ldapInterface != nil {
|
||||
ch.Ldap = ldapInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if notificationInterface != nil {
|
||||
ch.Notification = notificationInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
if samlInterface != nil {
|
||||
ch.Saml = samlInterface(New(ServerConnector(ch)))
|
||||
if err := ch.Saml.ConfigureSP(request.EmptyContext(s.Log())); err != nil {
|
||||
s.Log().Error("An error occurred while configuring SAML Service Provider", mlog.Err(err))
|
||||
}
|
||||
|
||||
ch.AddConfigListener(func(_, _ *model.Config) {
|
||||
if err := ch.Saml.ConfigureSP(request.EmptyContext(s.Log())); err != nil {
|
||||
s.Log().Error("An error occurred while configuring SAML Service Provider", mlog.Err(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if intuneInterface != nil {
|
||||
ch.Intune = intuneInterface(New(ServerConnector(ch)))
|
||||
}
|
||||
|
||||
if pushProxyInterface != nil {
|
||||
app := New(ServerConnector(ch))
|
||||
s.PushProxy = pushProxyInterface(app)
|
||||
|
||||
// Add config listener to regenerate token when push proxy URL changes
|
||||
app.AddConfigListener(func(oldCfg, newCfg *model.Config) {
|
||||
// Only cluster leader should regenerate to avoid duplicate requests
|
||||
if !app.IsLeader() {
|
||||
return
|
||||
}
|
||||
|
||||
oldURL := model.SafeDereference(oldCfg.EmailSettings.PushNotificationServer)
|
||||
newURL := model.SafeDereference(newCfg.EmailSettings.PushNotificationServer)
|
||||
|
||||
// If push proxy URL changed
|
||||
if oldURL != newURL {
|
||||
if newURL != "" {
|
||||
// URL changed to a new value, regenerate token
|
||||
s.Log().Info("Push notification server URL changed, regenerating auth token",
|
||||
mlog.String("old_url", oldURL),
|
||||
mlog.String("new_url", newURL))
|
||||
|
||||
if err := s.PushProxy.GenerateAuthToken(); err != nil {
|
||||
s.Log().Error("Failed to regenerate auth token after config change", mlog.Err(err))
|
||||
}
|
||||
} else if oldURL != "" {
|
||||
// URL was cleared, delete the old token
|
||||
s.Log().Info("Push notification server URL cleared, removing auth token")
|
||||
if err := s.PushProxy.DeleteAuthToken(); err != nil {
|
||||
s.Log().Error("Failed to delete auth token after URL cleared", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if accessControlServiceInterface != nil {
|
||||
app := New(ServerConnector(ch))
|
||||
ch.AccessControl = accessControlServiceInterface(app)
|
||||
|
||||
appErr := ch.AccessControl.Init(request.EmptyContext(s.Log()))
|
||||
if appErr != nil && appErr.StatusCode != http.StatusNotImplemented {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
|
||||
app.AddLicenseListener(func(newCfg, old *model.License) {
|
||||
if ch.AccessControl != nil {
|
||||
if appErr := ch.AccessControl.Init(request.EmptyContext(s.Log())); appErr != nil && appErr.StatusCode != http.StatusNotImplemented {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var imgErr error
|
||||
decoderConcurrency := int(*ch.cfgSvc.Config().FileSettings.MaxImageDecoderConcurrency)
|
||||
if decoderConcurrency == -1 {
|
||||
decoderConcurrency = runtime.NumCPU()
|
||||
}
|
||||
ch.imgDecoder, imgErr = imaging.NewDecoder(imaging.DecoderOptions{
|
||||
ConcurrencyLevel: decoderConcurrency,
|
||||
MaxDecodedResolution: *ch.cfgSvc.Config().FileSettings.MaxImageResolution,
|
||||
})
|
||||
if imgErr != nil {
|
||||
return nil, errors.Wrap(imgErr, "failed to create image decoder")
|
||||
}
|
||||
ch.imgEncoder, imgErr = imaging.NewEncoder(imaging.EncoderOptions{
|
||||
ConcurrencyLevel: runtime.NumCPU(),
|
||||
})
|
||||
if imgErr != nil {
|
||||
return nil, errors.Wrap(imgErr, "failed to create image encoder")
|
||||
}
|
||||
|
||||
// Setup routes.
|
||||
pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
|
||||
pluginsRoute.HandleFunc("", ch.ServePluginRequest)
|
||||
pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest)
|
||||
pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest)
|
||||
|
||||
if err := ch.reloadGuardCache(request.EmptyContext(s.Log()), s.Store()); err != nil {
|
||||
s.Log().Warn(
|
||||
"Failed to load channel guard cache at startup; retry scheduled",
|
||||
mlog.Bool("clustered", s.platform.Cluster() != nil),
|
||||
mlog.Err(err),
|
||||
)
|
||||
ch.scheduleGuardCacheReloadRetry()
|
||||
}
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (ch *Channels) SetAgentsBridge(bridge AgentsBridge) {
|
||||
ch.agentsBridge = bridge
|
||||
}
|
||||
|
||||
func (ch *Channels) Start() error {
|
||||
// Start plugins
|
||||
ctx := request.EmptyContext(ch.srv.Log())
|
||||
ch.initPlugins(ctx, *ch.cfgSvc.Config().PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory)
|
||||
|
||||
interruptChan := make(chan os.Signal, 1)
|
||||
signal.Notify(interruptChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
select {
|
||||
case <-interruptChan:
|
||||
if err := ch.Stop(); err != nil {
|
||||
ch.srv.Log().Warn("Error stopping channels", mlog.Err(err))
|
||||
}
|
||||
os.Exit(1)
|
||||
case <-ch.interruptQuitChan:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
ch.AddConfigListener(func(prevCfg, cfg *model.Config) {
|
||||
// We compute the difference between configs
|
||||
// to ensure we don't re-init plugins unnecessarily.
|
||||
diffs, err := config.Diff(prevCfg, cfg)
|
||||
if err != nil {
|
||||
ch.srv.Log().Warn("Error in comparing configs", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
hasDiff := false
|
||||
// TODO: This could be a method on ConfigDiffs itself
|
||||
for _, diff := range diffs {
|
||||
if strings.HasPrefix(diff.Path, "PluginSettings.") {
|
||||
hasDiff = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Do only if some plugin related settings has changed.
|
||||
if hasDiff {
|
||||
if *cfg.PluginSettings.Enable {
|
||||
ch.initPlugins(ctx, *cfg.PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory)
|
||||
} else {
|
||||
ch.ShutDownPlugins()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ch.AddConfigListener(ch.clearABACRenderCachesOnFlip)
|
||||
|
||||
// TODO: This should be moved to the platform service.
|
||||
if err := ch.srv.platform.EnsureAsymmetricSigningKey(); err != nil {
|
||||
return errors.Wrapf(err, "unable to ensure asymmetric signing key")
|
||||
}
|
||||
|
||||
if err := ch.ensurePostActionCookieSecret(); err != nil {
|
||||
return errors.Wrapf(err, "unable to ensure PostAction cookie secret")
|
||||
}
|
||||
|
||||
if err := ch.ensureSamlRelayStateSigningKey(); err != nil {
|
||||
return errors.Wrapf(err, "unable to ensure SAML RelayState signing key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Channels) Stop() error {
|
||||
ch.ShutDownPlugins()
|
||||
|
||||
ch.dndTaskMut.Lock()
|
||||
if ch.dndTask != nil {
|
||||
ch.dndTask.Cancel()
|
||||
}
|
||||
ch.dndTaskMut.Unlock()
|
||||
|
||||
close(ch.interruptQuitChan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Channels) AddConfigListener(listener func(*model.Config, *model.Config)) string {
|
||||
return ch.cfgSvc.AddConfigListener(listener)
|
||||
}
|
||||
|
||||
func (ch *Channels) RemoveConfigListener(id string) {
|
||||
ch.cfgSvc.RemoveConfigListener(id)
|
||||
}
|
||||
|
||||
func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks, manifest *model.Manifest) bool, hookId int) {
|
||||
if env := ch.GetPluginsEnvironment(); env != nil {
|
||||
env.RunMultiPluginHook(hookRunnerFunc, hookId)
|
||||
}
|
||||
}
|
||||
|
||||
// RunMultiHookExcluding is like RunMultiHook but skips plugins whose IDs appear in excludePluginIDs.
|
||||
// Fail-open semantics are preserved.
|
||||
func (ch *Channels) RunMultiHookExcluding(excludePluginIDs []string, hookRunnerFunc func(plugin.Hooks, *model.Manifest) bool, hookId int) {
|
||||
if env := ch.GetPluginsEnvironment(); env != nil {
|
||||
env.RunMultiPluginHookExcluding(excludePluginIDs, hookRunnerFunc, hookId)
|
||||
}
|
||||
}
|
||||
|
||||
// RunMultiHookWithRPCErr dispatches a hook closure across active plugins, surfacing RPC transport
|
||||
// errors. Returns nil in two cases that callers must distinguish themselves: (a) the plugin
|
||||
// environment is unavailable (plugins disabled, or not yet initialized), so the closure was never
|
||||
// invoked; (b) iteration completed and every closure invocation returned nil. Callers that need
|
||||
// fail-closed semantics on case (a) must check `ch.GetPluginsEnvironment() != nil` at the call site
|
||||
// before invoking — the right policy when plugins are disabled by config is caller-specific.
|
||||
func (ch *Channels) RunMultiHookWithRPCErr(hookRunnerFunc func(hooks plugin.HooksWithRPCErr, manifest *model.Manifest) (bool, error), hookId int) error {
|
||||
if env := ch.GetPluginsEnvironment(); env != nil {
|
||||
return env.RunMultiPluginHookWithRPCErr(hookRunnerFunc, hookId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Channels) HooksForPlugin(id string) (plugin.Hooks, error) {
|
||||
env := ch.GetPluginsEnvironment()
|
||||
if env == nil {
|
||||
return nil, errors.New("plugins are not initialized")
|
||||
}
|
||||
|
||||
hooks, err := env.HooksForPlugin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hooks, nil
|
||||
}
|
||||
|
||||
// HooksForPluginWithRPCErr returns the full *WithRPCErr hook surface for the named plugin.
|
||||
// Returns an error if the plugin environment is unavailable, the plugin is not found, or not active.
|
||||
func (ch *Channels) HooksForPluginWithRPCErr(id string) (plugin.HooksWithRPCErr, error) {
|
||||
env := ch.GetPluginsEnvironment()
|
||||
if env == nil {
|
||||
return nil, errors.New("plugin environment not available")
|
||||
}
|
||||
return env.HooksForPluginWithRPCErr(id)
|
||||
}
|
||||
Reference in New Issue
Block a user