Files
gochat/internal/channel/registry.go
T
2026-06-04 15:44:48 +08:00

161 lines
5.1 KiB
Go

package channel
import (
"context"
"fmt"
"sync"
"github.com/gochat/gochat/internal/model"
)
// ChannelRegistry is the plugin registration center for channel providers.
// Reference: P2D §4 — replaces Chatwoot's channel_type enum with runtime registration
// New channels just implement ChannelProvider + call Register(), no core code changes needed.
type ChannelRegistry struct {
mu sync.RWMutex
providers map[ChannelType]ChannelProvider
}
// Global registry instance — all providers register here via init() or explicit call
var globalRegistry = &ChannelRegistry{
providers: make(map[ChannelType]ChannelProvider),
}
// Register adds a channel provider to the global registry.
// Returns error if a provider of the same type is already registered.
// Pattern: each channel package calls channel.Register() in its init() function.
func Register(provider ChannelProvider) error {
globalRegistry.mu.Lock()
defer globalRegistry.mu.Unlock()
ct := provider.Type()
if _, exists := globalRegistry.providers[ct]; exists {
return fmt.Errorf("channel type %s already registered", ct)
}
globalRegistry.providers[ct] = provider
return nil
}
// MustRegister is like Register but panics on duplicate registration.
// Used in init() functions where failure is a programming error.
func MustRegister(provider ChannelProvider) {
if err := Register(provider); err != nil {
panic(err)
}
}
// Get retrieves a channel provider by type from the global registry.
func Get(ct ChannelType) (ChannelProvider, error) {
globalRegistry.mu.RLock()
defer globalRegistry.mu.RUnlock()
provider, ok := globalRegistry.providers[ct]
if !ok {
return nil, fmt.Errorf("channel type %s not registered", ct)
}
return provider, nil
}
// MustGet is like Get but panics if provider not found.
func MustGet(ct ChannelType) ChannelProvider {
provider, err := Get(ct)
if err != nil {
panic(err)
}
return provider
}
// GetRegistry returns the global channel registry instance.
// Used by MessageBroker and other components that need access to the registry struct.
func GetRegistry() *ChannelRegistry {
return globalRegistry
}
// RegisterOnInstance adds a channel provider to this registry instance.
// Returns error if a provider of the same type is already registered.
// This is the instance-level counterpart of the global Register() function.
func (r *ChannelRegistry) RegisterOnInstance(provider ChannelProvider) error {
r.mu.Lock()
defer r.mu.Unlock()
ct := provider.Type()
if _, exists := r.providers[ct]; exists {
return fmt.Errorf("channel type %s already registered", ct)
}
r.providers[ct] = provider
return nil
}
// GetOnRegistry retrieves a channel provider by type from a specific registry instance.
func (r *ChannelRegistry) Get(ct ChannelType) (ChannelProvider, error) {
r.mu.RLock()
defer r.mu.RUnlock()
provider, ok := r.providers[ct]
if !ok {
return nil, fmt.Errorf("channel type %s not registered", ct)
}
return provider, nil
}
// List returns all registered channel types.
func List() []ChannelType {
globalRegistry.mu.RLock()
defer globalRegistry.mu.RUnlock()
types := make([]ChannelType, 0, len(globalRegistry.providers))
for ct := range globalRegistry.providers {
types = append(types, ct)
}
return types
}
// ListProviders returns all registered provider instances.
func ListProviders() []ChannelProvider {
globalRegistry.mu.RLock()
defer globalRegistry.mu.RUnlock()
providers := make([]ChannelProvider, 0, len(globalRegistry.providers))
for _, p := range globalRegistry.providers {
providers = append(providers, p)
}
return providers
}
// IsRegistered checks if a channel type is registered.
func IsRegistered(ct ChannelType) bool {
globalRegistry.mu.RLock()
defer globalRegistry.mu.RUnlock()
_, exists := globalRegistry.providers[ct]
return exists
}
// ProcessIncomingMessage is the unified inbound message processing pipeline.
// Reference: P2D §8 — all inbound messages flow through this pipeline
// This is the primary entry point for webhook handlers and polling workers.
func ProcessIncomingMessage(ctx context.Context, ct ChannelType, inbox *model.Inbox, rawPayload []byte) (*IncomingMessage, error) {
provider, err := Get(ct)
if err != nil {
return nil, fmt.Errorf("no provider for channel type %s: %w", ct, err)
}
return provider.ProcessIncoming(ctx, inbox, rawPayload)
}
// SendMessageToChannel is the unified outbound message sending pipeline.
// Reference: P2D §8 — all outbound messages flow through this pipeline
func SendMessageToChannel(ctx context.Context, ct ChannelType, inbox *model.Inbox, message *model.Message, contact *model.Contact) (*SendResult, error) {
provider, err := Get(ct)
if err != nil {
return nil, fmt.Errorf("no provider for channel type %s: %w", ct, err)
}
return provider.SendMessage(ctx, inbox, message, contact)
}
// ValidateWebhookRequest delegates webhook validation to the appropriate provider.
func ValidateWebhookRequest(ctx context.Context, ct ChannelType, inbox *model.Inbox, request *WebhookRequest) error {
provider, err := Get(ct)
if err != nil {
return fmt.Errorf("no provider for channel type %s: %w", ct, err)
}
return provider.ValidateWebhookRequest(ctx, inbox, request)
}