161 lines
5.1 KiB
Go
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)
|
|
} |