ironnotify-go/client.go

372 lines
8.4 KiB
Go

package ironnotify
import (
"context"
"errors"
"fmt"
"sync"
)
// Errors
var (
ErrNotInitialized = errors.New("ironnotify: client not initialized")
ErrTitleRequired = errors.New("ironnotify: notification title is required")
ErrAPIKeyRequired = errors.New("ironnotify: API key is required")
)
// Client is the IronNotify client.
type Client struct {
options Options
transport *Transport
queue *OfflineQueue
isOnline bool
connectionState ConnectionState
onNotification NotificationHandler
onUnreadCount UnreadCountHandler
onConnectionChange ConnectionStateHandler
mu sync.RWMutex
}
// Global client instance
var globalClient *Client
var globalMu sync.RWMutex
// NewClient creates a new IronNotify client.
func NewClient(options Options) (*Client, error) {
if options.APIKey == "" {
return nil, ErrAPIKeyRequired
}
opts := options.WithDefaults()
client := &Client{
options: opts,
transport: NewTransport(opts.APIBaseURL, opts.APIKey, opts.HTTPTimeout, opts.Debug),
isOnline: true,
connectionState: StateDisconnected,
}
if opts.EnableOfflineQueue {
client.queue = NewOfflineQueue(opts.MaxOfflineQueueSize, opts.Debug)
}
if opts.Debug {
fmt.Println("[IronNotify] Client initialized")
}
return client, nil
}
// Init initializes the global client.
func Init(apiKey string, opts ...func(*Options)) error {
options := DefaultOptions()
options.APIKey = apiKey
for _, opt := range opts {
opt(&options)
}
client, err := NewClient(options)
if err != nil {
return err
}
globalMu.Lock()
globalClient = client
globalMu.Unlock()
return nil
}
// WithDebug enables debug mode.
func WithDebug(debug bool) func(*Options) {
return func(o *Options) {
o.Debug = debug
}
}
// WithAPIBaseURL sets the API base URL.
func WithAPIBaseURL(url string) func(*Options) {
return func(o *Options) {
o.APIBaseURL = url
}
}
// WithOfflineQueue enables or disables the offline queue.
func WithOfflineQueue(enabled bool) func(*Options) {
return func(o *Options) {
o.EnableOfflineQueue = enabled
}
}
// getGlobalClient returns the global client.
func getGlobalClient() (*Client, error) {
globalMu.RLock()
defer globalMu.RUnlock()
if globalClient == nil {
return nil, ErrNotInitialized
}
return globalClient, nil
}
// Notify sends a notification using the global client.
func Notify(eventType, title string, opts ...NotifyOption) SendResult {
client, err := getGlobalClient()
if err != nil {
return SendResult{Success: false, Error: err.Error()}
}
return client.Notify(eventType, title, opts...)
}
// NotifyOption is a function that modifies a NotificationPayload.
type NotifyOption func(*NotificationPayload)
// WithMessage sets the message.
func WithMessage(message string) NotifyOption {
return func(p *NotificationPayload) {
p.Message = message
}
}
// WithSeverity sets the severity.
func WithSeverity(severity SeverityLevel) NotifyOption {
return func(p *NotificationPayload) {
p.Severity = severity
}
}
// WithUserID sets the user ID.
func WithUserID(userID string) NotifyOption {
return func(p *NotificationPayload) {
p.UserID = userID
}
}
// WithMetadataOpt sets metadata.
func WithMetadataOpt(metadata map[string]any) NotifyOption {
return func(p *NotificationPayload) {
p.Metadata = metadata
}
}
// Notify sends a notification.
func (c *Client) Notify(eventType, title string, opts ...NotifyOption) SendResult {
payload := &NotificationPayload{
EventType: eventType,
Title: title,
Severity: SeverityInfo,
}
for _, opt := range opts {
opt(payload)
}
return c.SendPayload(payload)
}
// Event creates an event builder using the global client.
func Event(eventType string) *EventBuilder {
client, err := getGlobalClient()
if err != nil {
// Return a builder that will fail on send
return &EventBuilder{eventType: eventType}
}
return client.Event(eventType)
}
// Event creates an event builder.
func (c *Client) Event(eventType string) *EventBuilder {
return newEventBuilder(c, eventType)
}
// SendPayload sends a notification payload.
func (c *Client) SendPayload(payload *NotificationPayload) SendResult {
result := c.transport.Send(context.Background(), payload)
if !result.Success && c.options.EnableOfflineQueue && c.queue != nil {
c.queue.Add(*payload)
c.mu.Lock()
c.isOnline = false
c.mu.Unlock()
return SendResult{
Success: result.Success,
NotificationID: result.NotificationID,
Error: result.Error,
Queued: true,
}
}
return result
}
// GetNotifications retrieves notifications.
func (c *Client) GetNotifications(limit, offset int, unreadOnly bool) ([]Notification, error) {
return c.transport.GetNotifications(context.Background(), limit, offset, unreadOnly)
}
// GetNotificationsContext retrieves notifications with context.
func (c *Client) GetNotificationsContext(ctx context.Context, limit, offset int, unreadOnly bool) ([]Notification, error) {
return c.transport.GetNotifications(ctx, limit, offset, unreadOnly)
}
// GetUnreadCount returns the unread notification count.
func (c *Client) GetUnreadCount() (int, error) {
return c.transport.GetUnreadCount(context.Background())
}
// MarkAsRead marks a notification as read.
func (c *Client) MarkAsRead(notificationID string) error {
return c.transport.MarkAsRead(context.Background(), notificationID)
}
// MarkAllAsRead marks all notifications as read.
func (c *Client) MarkAllAsRead() error {
return c.transport.MarkAllAsRead(context.Background())
}
// OnNotification sets the notification handler.
func (c *Client) OnNotification(handler NotificationHandler) {
c.mu.Lock()
defer c.mu.Unlock()
c.onNotification = handler
}
// OnUnreadCountChange sets the unread count change handler.
func (c *Client) OnUnreadCountChange(handler UnreadCountHandler) {
c.mu.Lock()
defer c.mu.Unlock()
c.onUnreadCount = handler
}
// OnConnectionStateChange sets the connection state change handler.
func (c *Client) OnConnectionStateChange(handler ConnectionStateHandler) {
c.mu.Lock()
defer c.mu.Unlock()
c.onConnectionChange = handler
}
// ConnectionState returns the current connection state.
func (c *Client) ConnectionState() ConnectionState {
c.mu.RLock()
defer c.mu.RUnlock()
return c.connectionState
}
// Connect connects to real-time notifications.
func (c *Client) Connect() {
c.setConnectionState(StateConnected)
if c.options.Debug {
fmt.Println("[IronNotify] Connected (WebSocket not implemented)")
}
}
// Disconnect disconnects from real-time notifications.
func (c *Client) Disconnect() {
c.setConnectionState(StateDisconnected)
}
// SubscribeToUser subscribes to a user's notifications.
func (c *Client) SubscribeToUser(userID string) {
if c.options.Debug {
fmt.Printf("[IronNotify] Subscribed to user: %s\n", userID)
}
}
// SubscribeToApp subscribes to app-wide notifications.
func (c *Client) SubscribeToApp() {
if c.options.Debug {
fmt.Println("[IronNotify] Subscribed to app notifications")
}
}
// Flush flushes the offline queue.
func (c *Client) Flush() {
if c.queue == nil || c.queue.IsEmpty() {
return
}
if !c.transport.IsOnline(context.Background()) {
return
}
c.mu.Lock()
c.isOnline = true
c.mu.Unlock()
notifications := c.queue.GetAll()
for i := len(notifications) - 1; i >= 0; i-- {
result := c.transport.Send(context.Background(), &notifications[i])
if result.Success {
c.queue.Remove(i)
} else {
break
}
}
}
// FlushContext flushes the offline queue with context.
func (c *Client) FlushContext(ctx context.Context) {
if c.queue == nil || c.queue.IsEmpty() {
return
}
if !c.transport.IsOnline(ctx) {
return
}
c.mu.Lock()
c.isOnline = true
c.mu.Unlock()
notifications := c.queue.GetAll()
for i := len(notifications) - 1; i >= 0; i-- {
result := c.transport.Send(ctx, &notifications[i])
if result.Success {
c.queue.Remove(i)
} else {
break
}
}
}
// Close closes the client.
func (c *Client) Close() {
c.Disconnect()
c.transport.Close()
}
// setConnectionState sets the connection state and calls the handler.
func (c *Client) setConnectionState(state ConnectionState) {
c.mu.Lock()
c.connectionState = state
handler := c.onConnectionChange
c.mu.Unlock()
if handler != nil {
handler(state)
}
}
// Flush flushes the global client's offline queue.
func Flush() {
client, err := getGlobalClient()
if err != nil {
return
}
client.Flush()
}
// Close closes the global client.
func Close() {
globalMu.Lock()
defer globalMu.Unlock()
if globalClient != nil {
globalClient.Close()
globalClient = nil
}
}