372 lines
8.4 KiB
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(), ¬ifications[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, ¬ifications[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
|
|
}
|
|
}
|