Implement IronNotify Go SDK
- Client with global and instance-based usage - Fluent EventBuilder for complex notifications - HTTP transport with context support - Offline queue with JSON persistence - Severity levels and notification actions - Thread-safe operations with sync.RWMutex - Full README with examples
This commit is contained in:
parent
7ce102c947
commit
961d963feb
|
|
@ -0,0 +1,28 @@
|
|||
# Binaries
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary
|
||||
*.test
|
||||
|
||||
# Output of go coverage tool
|
||||
*.out
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Local storage
|
||||
.ironnotify/
|
||||
293
README.md
293
README.md
|
|
@ -1,2 +1,291 @@
|
|||
# ironnotify-go
|
||||
IronNotify SDK for Go - Event notifications and alerts
|
||||
# IronNotify SDK for Go
|
||||
|
||||
Event notifications and alerts SDK for Go applications. Send notifications, receive real-time updates, and manage notification state.
|
||||
|
||||
[](https://pkg.go.dev/github.com/IronServices/ironnotify-go)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/IronServices/ironnotify-go
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Send a Simple Notification
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ironnotify "github.com/IronServices/ironnotify-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize
|
||||
err := ironnotify.Init("ak_live_xxxxx")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ironnotify.Close()
|
||||
|
||||
// Send a simple notification
|
||||
result := ironnotify.Notify(
|
||||
"order.created",
|
||||
"New Order Received",
|
||||
ironnotify.WithMessage("Order #1234 has been placed"),
|
||||
ironnotify.WithSeverity(ironnotify.SeveritySuccess),
|
||||
ironnotify.WithMetadataOpt(map[string]any{
|
||||
"order_id": "1234",
|
||||
"amount": 99.99,
|
||||
}),
|
||||
)
|
||||
|
||||
if result.Success {
|
||||
fmt.Println("Notification sent:", result.NotificationID)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fluent Event Builder
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
ironnotify "github.com/IronServices/ironnotify-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ironnotify.Init("ak_live_xxxxx")
|
||||
defer ironnotify.Close()
|
||||
|
||||
// Build complex notifications with the fluent API
|
||||
result := ironnotify.Event("payment.failed").
|
||||
WithTitle("Payment Failed").
|
||||
WithMessage("Payment could not be processed").
|
||||
WithSeverity(ironnotify.SeverityError).
|
||||
WithMetadata("order_id", "1234").
|
||||
WithMetadata("reason", "Card declined").
|
||||
WithAction("Retry Payment", ironnotify.ActionURL("/orders/1234/retry"), ironnotify.ActionStyle("primary")).
|
||||
WithAction("Contact Support", ironnotify.ActionHandler("open_support")).
|
||||
ForUser("user-123").
|
||||
WithDeduplicationKey("payment-failed-1234").
|
||||
ExpiresIn(24 * time.Hour).
|
||||
Send()
|
||||
|
||||
if result.Queued {
|
||||
// Notification was queued for later (offline mode)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using the Client Directly
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
ironnotify "github.com/IronServices/ironnotify-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := ironnotify.NewClient(ironnotify.Options{
|
||||
APIKey: "ak_live_xxxxx",
|
||||
Debug: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Send notification
|
||||
result := client.Notify("event.type", "Title")
|
||||
|
||||
// Use event builder
|
||||
result = client.Event("event.type").
|
||||
WithTitle("Title").
|
||||
Send()
|
||||
|
||||
// Get notifications
|
||||
notifications, err := client.GetNotifications(10, 0, false)
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
import ironnotify "github.com/IronServices/ironnotify-go"
|
||||
|
||||
client, _ := ironnotify.NewClient(ironnotify.Options{
|
||||
APIKey: "ak_live_xxxxx",
|
||||
APIBaseURL: "https://api.ironnotify.com",
|
||||
WebSocketURL: "wss://ws.ironnotify.com",
|
||||
Debug: false,
|
||||
EnableOfflineQueue: true,
|
||||
MaxOfflineQueueSize: 100,
|
||||
AutoReconnect: true,
|
||||
MaxReconnectAttempts: 5,
|
||||
ReconnectDelay: time.Second,
|
||||
HTTPTimeout: 30 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `APIKey` | string | required | Your API key (ak_live_xxx or ak_test_xxx) |
|
||||
| `APIBaseURL` | string | https://api.ironnotify.com | API base URL |
|
||||
| `WebSocketURL` | string | wss://ws.ironnotify.com | WebSocket URL |
|
||||
| `Debug` | bool | false | Enable debug logging |
|
||||
| `EnableOfflineQueue` | bool | true | Queue notifications when offline |
|
||||
| `MaxOfflineQueueSize` | int | 100 | Max offline queue size |
|
||||
| `AutoReconnect` | bool | true | Auto-reconnect WebSocket |
|
||||
| `MaxReconnectAttempts` | int | 5 | Max reconnection attempts |
|
||||
| `ReconnectDelay` | Duration | 1s | Base reconnection delay |
|
||||
| `HTTPTimeout` | Duration | 30s | HTTP request timeout |
|
||||
|
||||
## Severity Levels
|
||||
|
||||
```go
|
||||
ironnotify.SeverityInfo // "info"
|
||||
ironnotify.SeveritySuccess // "success"
|
||||
ironnotify.SeverityWarning // "warning"
|
||||
ironnotify.SeverityError // "error"
|
||||
ironnotify.SeverityCritical // "critical"
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
```go
|
||||
ironnotify.Event("order.shipped").
|
||||
WithTitle("Order Shipped").
|
||||
WithAction("Track Package",
|
||||
ironnotify.ActionURL("https://tracking.example.com/123"),
|
||||
ironnotify.ActionStyle("primary")).
|
||||
WithAction("View Order",
|
||||
ironnotify.ActionHandler("view_order")).
|
||||
Send()
|
||||
```
|
||||
|
||||
## Deduplication
|
||||
|
||||
Prevent duplicate notifications:
|
||||
|
||||
```go
|
||||
ironnotify.Event("reminder").
|
||||
WithTitle("Daily Reminder").
|
||||
WithDeduplicationKey("daily-reminder-2024-01-15").
|
||||
Send()
|
||||
```
|
||||
|
||||
## Grouping
|
||||
|
||||
Group related notifications:
|
||||
|
||||
```go
|
||||
ironnotify.Event("comment.new").
|
||||
WithTitle("New Comment").
|
||||
WithGroupKey("post-123-comments").
|
||||
Send()
|
||||
```
|
||||
|
||||
## Expiration
|
||||
|
||||
```go
|
||||
// Expires in 1 hour
|
||||
ironnotify.Event("flash_sale").
|
||||
WithTitle("Flash Sale!").
|
||||
ExpiresIn(time.Hour).
|
||||
Send()
|
||||
|
||||
// Expires at specific time
|
||||
ironnotify.Event("event_reminder").
|
||||
WithTitle("Event Tomorrow").
|
||||
ExpiresAt(time.Now().Add(24 * time.Hour)).
|
||||
Send()
|
||||
```
|
||||
|
||||
## Managing Notifications
|
||||
|
||||
### Get Notifications
|
||||
|
||||
```go
|
||||
// Get all notifications
|
||||
notifications, err := client.GetNotifications(0, 0, false)
|
||||
|
||||
// With pagination and filters
|
||||
unread, err := client.GetNotifications(10, 0, true) // limit=10, unread only
|
||||
|
||||
// With context
|
||||
notifications, err := client.GetNotificationsContext(ctx, 10, 0, false)
|
||||
```
|
||||
|
||||
### Mark as Read
|
||||
|
||||
```go
|
||||
// Mark single notification
|
||||
err := client.MarkAsRead("notification-id")
|
||||
|
||||
// Mark all as read
|
||||
err := client.MarkAllAsRead()
|
||||
```
|
||||
|
||||
### Get Unread Count
|
||||
|
||||
```go
|
||||
count, err := client.GetUnreadCount()
|
||||
fmt.Printf("You have %d unread notifications\n", count)
|
||||
```
|
||||
|
||||
## Real-Time Notifications
|
||||
|
||||
```go
|
||||
client.OnNotification(func(n ironnotify.Notification) {
|
||||
fmt.Printf("New notification: %s\n", n.Title)
|
||||
})
|
||||
|
||||
client.OnUnreadCountChange(func(count int) {
|
||||
fmt.Printf("Unread count: %d\n", count)
|
||||
})
|
||||
|
||||
client.OnConnectionStateChange(func(state ironnotify.ConnectionState) {
|
||||
fmt.Printf("Connection state: %s\n", state)
|
||||
})
|
||||
|
||||
client.Connect()
|
||||
client.SubscribeToUser("user-123")
|
||||
client.SubscribeToApp()
|
||||
```
|
||||
|
||||
## Offline Support
|
||||
|
||||
Notifications are automatically queued when offline:
|
||||
|
||||
```go
|
||||
// This will be queued if offline
|
||||
ironnotify.Notify("event", "Title")
|
||||
|
||||
// Manually flush the queue
|
||||
ironnotify.Flush()
|
||||
|
||||
// Or with context
|
||||
client.FlushContext(ctx)
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The client is thread-safe and can be used from multiple goroutines concurrently.
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](https://www.ironnotify.com/docs)
|
||||
- [Dashboard](https://www.ironnotify.com)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
package ironnotify
|
||||
|
||||
import "time"
|
||||
|
||||
// EventBuilder provides a fluent API for building notifications.
|
||||
type EventBuilder struct {
|
||||
client *Client
|
||||
eventType string
|
||||
title string
|
||||
message string
|
||||
severity SeverityLevel
|
||||
metadata map[string]any
|
||||
actions []NotificationAction
|
||||
userID string
|
||||
groupKey string
|
||||
deduplicationKey string
|
||||
expiresAt *time.Time
|
||||
}
|
||||
|
||||
// newEventBuilder creates a new EventBuilder.
|
||||
func newEventBuilder(client *Client, eventType string) *EventBuilder {
|
||||
return &EventBuilder{
|
||||
client: client,
|
||||
eventType: eventType,
|
||||
severity: SeverityInfo,
|
||||
metadata: make(map[string]any),
|
||||
actions: make([]NotificationAction, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// WithTitle sets the notification title.
|
||||
func (b *EventBuilder) WithTitle(title string) *EventBuilder {
|
||||
b.title = title
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMessage sets the notification message.
|
||||
func (b *EventBuilder) WithMessage(message string) *EventBuilder {
|
||||
b.message = message
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSeverity sets the severity level.
|
||||
func (b *EventBuilder) WithSeverity(severity SeverityLevel) *EventBuilder {
|
||||
b.severity = severity
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMetadata adds metadata to the notification.
|
||||
func (b *EventBuilder) WithMetadata(key string, value any) *EventBuilder {
|
||||
b.metadata[key] = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMetadataMap adds multiple metadata entries.
|
||||
func (b *EventBuilder) WithMetadataMap(metadata map[string]any) *EventBuilder {
|
||||
for k, v := range metadata {
|
||||
b.metadata[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAction adds an action button to the notification.
|
||||
func (b *EventBuilder) WithAction(label string, opts ...ActionOption) *EventBuilder {
|
||||
action := NotificationAction{Label: label, Style: "default"}
|
||||
for _, opt := range opts {
|
||||
opt(&action)
|
||||
}
|
||||
b.actions = append(b.actions, action)
|
||||
return b
|
||||
}
|
||||
|
||||
// ActionOption is a function that modifies a NotificationAction.
|
||||
type ActionOption func(*NotificationAction)
|
||||
|
||||
// ActionURL sets the URL for an action.
|
||||
func ActionURL(url string) ActionOption {
|
||||
return func(a *NotificationAction) {
|
||||
a.URL = url
|
||||
}
|
||||
}
|
||||
|
||||
// ActionHandler sets the action handler name.
|
||||
func ActionHandler(action string) ActionOption {
|
||||
return func(a *NotificationAction) {
|
||||
a.Action = action
|
||||
}
|
||||
}
|
||||
|
||||
// ActionStyle sets the style for an action.
|
||||
func ActionStyle(style string) ActionOption {
|
||||
return func(a *NotificationAction) {
|
||||
a.Style = style
|
||||
}
|
||||
}
|
||||
|
||||
// ForUser sets the target user ID.
|
||||
func (b *EventBuilder) ForUser(userID string) *EventBuilder {
|
||||
b.userID = userID
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupKey sets the group key for grouping related notifications.
|
||||
func (b *EventBuilder) WithGroupKey(groupKey string) *EventBuilder {
|
||||
b.groupKey = groupKey
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeduplicationKey sets the deduplication key.
|
||||
func (b *EventBuilder) WithDeduplicationKey(key string) *EventBuilder {
|
||||
b.deduplicationKey = key
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresIn sets the expiration time from now.
|
||||
func (b *EventBuilder) ExpiresIn(duration time.Duration) *EventBuilder {
|
||||
t := time.Now().Add(duration)
|
||||
b.expiresAt = &t
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresAt sets the expiration time.
|
||||
func (b *EventBuilder) ExpiresAt(t time.Time) *EventBuilder {
|
||||
b.expiresAt = &t
|
||||
return b
|
||||
}
|
||||
|
||||
// Build builds the notification payload.
|
||||
func (b *EventBuilder) Build() (*NotificationPayload, error) {
|
||||
if b.title == "" {
|
||||
return nil, ErrTitleRequired
|
||||
}
|
||||
|
||||
payload := &NotificationPayload{
|
||||
EventType: b.eventType,
|
||||
Title: b.title,
|
||||
Message: b.message,
|
||||
Severity: b.severity,
|
||||
UserID: b.userID,
|
||||
GroupKey: b.groupKey,
|
||||
DeduplicationKey: b.deduplicationKey,
|
||||
ExpiresAt: b.expiresAt,
|
||||
}
|
||||
|
||||
if len(b.metadata) > 0 {
|
||||
payload.Metadata = b.metadata
|
||||
}
|
||||
|
||||
if len(b.actions) > 0 {
|
||||
payload.Actions = b.actions
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// Send sends the notification.
|
||||
func (b *EventBuilder) Send() SendResult {
|
||||
payload, err := b.Build()
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
return b.client.SendPayload(payload)
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package ironnotify
|
||||
|
||||
import "time"
|
||||
|
||||
// Options configures the IronNotify client.
|
||||
type Options struct {
|
||||
// APIKey is the API key for authentication (required).
|
||||
// Format: ak_live_xxx or ak_test_xxx
|
||||
APIKey string
|
||||
|
||||
// APIBaseURL is the base URL for the IronNotify API.
|
||||
// Default: https://api.ironnotify.com
|
||||
APIBaseURL string
|
||||
|
||||
// WebSocketURL is the WebSocket URL for real-time notifications.
|
||||
// Default: wss://ws.ironnotify.com
|
||||
WebSocketURL string
|
||||
|
||||
// Debug enables debug logging.
|
||||
Debug bool
|
||||
|
||||
// EnableOfflineQueue enables offline notification queuing.
|
||||
// Default: true
|
||||
EnableOfflineQueue bool
|
||||
|
||||
// MaxOfflineQueueSize is the maximum number of notifications to queue offline.
|
||||
// Default: 100
|
||||
MaxOfflineQueueSize int
|
||||
|
||||
// AutoReconnect enables automatic WebSocket reconnection.
|
||||
// Default: true
|
||||
AutoReconnect bool
|
||||
|
||||
// MaxReconnectAttempts is the maximum number of reconnection attempts.
|
||||
// Default: 5
|
||||
MaxReconnectAttempts int
|
||||
|
||||
// ReconnectDelay is the base delay between reconnection attempts.
|
||||
// Default: 1 second
|
||||
ReconnectDelay time.Duration
|
||||
|
||||
// HTTPTimeout is the timeout for HTTP requests.
|
||||
// Default: 30 seconds
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultOptions returns the default configuration options.
|
||||
func DefaultOptions() Options {
|
||||
return Options{
|
||||
APIBaseURL: "https://api.ironnotify.com",
|
||||
WebSocketURL: "wss://ws.ironnotify.com",
|
||||
Debug: false,
|
||||
EnableOfflineQueue: true,
|
||||
MaxOfflineQueueSize: 100,
|
||||
AutoReconnect: true,
|
||||
MaxReconnectAttempts: 5,
|
||||
ReconnectDelay: time.Second,
|
||||
HTTPTimeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaults returns the options with default values applied.
|
||||
func (o Options) WithDefaults() Options {
|
||||
defaults := DefaultOptions()
|
||||
|
||||
if o.APIBaseURL == "" {
|
||||
o.APIBaseURL = defaults.APIBaseURL
|
||||
}
|
||||
if o.WebSocketURL == "" {
|
||||
o.WebSocketURL = defaults.WebSocketURL
|
||||
}
|
||||
if o.MaxOfflineQueueSize == 0 {
|
||||
o.MaxOfflineQueueSize = defaults.MaxOfflineQueueSize
|
||||
}
|
||||
if o.MaxReconnectAttempts == 0 {
|
||||
o.MaxReconnectAttempts = defaults.MaxReconnectAttempts
|
||||
}
|
||||
if o.ReconnectDelay == 0 {
|
||||
o.ReconnectDelay = defaults.ReconnectDelay
|
||||
}
|
||||
if o.HTTPTimeout == 0 {
|
||||
o.HTTPTimeout = defaults.HTTPTimeout
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/IronServices/ironnotify-go
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
)
|
||||
|
||||
require golang.org/x/net v0.17.0 // indirect
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package ironnotify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// OfflineQueue stores notifications when offline.
|
||||
type OfflineQueue struct {
|
||||
maxSize int
|
||||
debug bool
|
||||
queue []NotificationPayload
|
||||
storagePath string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewOfflineQueue creates a new OfflineQueue.
|
||||
func NewOfflineQueue(maxSize int, debug bool) *OfflineQueue {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
storagePath := filepath.Join(homeDir, ".ironnotify", "offline_queue.json")
|
||||
|
||||
q := &OfflineQueue{
|
||||
maxSize: maxSize,
|
||||
debug: debug,
|
||||
queue: make([]NotificationPayload, 0),
|
||||
storagePath: storagePath,
|
||||
}
|
||||
|
||||
q.loadFromStorage()
|
||||
return q
|
||||
}
|
||||
|
||||
// Add adds a notification to the queue.
|
||||
func (q *OfflineQueue) Add(payload NotificationPayload) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
if len(q.queue) >= q.maxSize {
|
||||
q.queue = q.queue[1:]
|
||||
if q.debug {
|
||||
fmt.Println("[IronNotify] Offline queue full, dropping oldest notification")
|
||||
}
|
||||
}
|
||||
|
||||
q.queue = append(q.queue, payload)
|
||||
q.saveToStorage()
|
||||
|
||||
if q.debug {
|
||||
fmt.Printf("[IronNotify] Notification queued for later: %s\n", payload.EventType)
|
||||
}
|
||||
}
|
||||
|
||||
// GetAll returns all queued notifications.
|
||||
func (q *OfflineQueue) GetAll() []NotificationPayload {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
result := make([]NotificationPayload, len(q.queue))
|
||||
copy(result, q.queue)
|
||||
return result
|
||||
}
|
||||
|
||||
// Remove removes a notification at the given index.
|
||||
func (q *OfflineQueue) Remove(index int) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
if index >= 0 && index < len(q.queue) {
|
||||
q.queue = append(q.queue[:index], q.queue[index+1:]...)
|
||||
q.saveToStorage()
|
||||
}
|
||||
}
|
||||
|
||||
// Clear clears the queue.
|
||||
func (q *OfflineQueue) Clear() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
q.queue = make([]NotificationPayload, 0)
|
||||
q.saveToStorage()
|
||||
}
|
||||
|
||||
// Size returns the queue size.
|
||||
func (q *OfflineQueue) Size() int {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return len(q.queue)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the queue is empty.
|
||||
func (q *OfflineQueue) IsEmpty() bool {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return len(q.queue) == 0
|
||||
}
|
||||
|
||||
func (q *OfflineQueue) loadFromStorage() {
|
||||
data, err := os.ReadFile(q.storagePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var queue []NotificationPayload
|
||||
if err := json.Unmarshal(data, &queue); err == nil {
|
||||
q.queue = queue
|
||||
}
|
||||
}
|
||||
|
||||
func (q *OfflineQueue) saveToStorage() {
|
||||
dir := filepath.Dir(q.storagePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(q.queue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.WriteFile(q.storagePath, data, 0644)
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
package ironnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Transport handles HTTP communication with the IronNotify API.
|
||||
type Transport struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
debug bool
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewTransport creates a new Transport.
|
||||
func NewTransport(baseURL, apiKey string, timeout time.Duration, debug bool) *Transport {
|
||||
return &Transport{
|
||||
baseURL: baseURL,
|
||||
apiKey: apiKey,
|
||||
debug: debug,
|
||||
httpClient: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends a notification payload to the server.
|
||||
func (t *Transport) Send(ctx context.Context, payload *NotificationPayload) SendResult {
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notify", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+t.apiKey)
|
||||
|
||||
if t.debug {
|
||||
fmt.Printf("[IronNotify] Sending notification: %s\n", payload.EventType)
|
||||
}
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
var result struct {
|
||||
NotificationID string `json:"notificationId"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err == nil {
|
||||
return SendResult{Success: true, NotificationID: result.NotificationID}
|
||||
}
|
||||
return SendResult{Success: true}
|
||||
}
|
||||
|
||||
var errorResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.Error != "" {
|
||||
return SendResult{Success: false, Error: errorResp.Error}
|
||||
}
|
||||
|
||||
return SendResult{Success: false, Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body))}
|
||||
}
|
||||
|
||||
// GetNotifications retrieves notifications from the server.
|
||||
func (t *Transport) GetNotifications(ctx context.Context, limit, offset int, unreadOnly bool) ([]Notification, error) {
|
||||
u, err := url.Parse(t.baseURL + "/api/v1/notifications")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
if limit > 0 {
|
||||
q.Set("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if offset > 0 {
|
||||
q.Set("offset", strconv.Itoa(offset))
|
||||
}
|
||||
if unreadOnly {
|
||||
q.Set("unread_only", "true")
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+t.apiKey)
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var notifications []Notification
|
||||
if err := json.NewDecoder(resp.Body).Decode(¬ifications); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
// GetUnreadCount retrieves the unread notification count.
|
||||
func (t *Transport) GetUnreadCount(ctx context.Context) (int, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"/api/v1/notifications/unread-count", nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+t.apiKey)
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Count int `json:"count"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.Count, nil
|
||||
}
|
||||
|
||||
// MarkAsRead marks a notification as read.
|
||||
func (t *Transport) MarkAsRead(ctx context.Context, notificationID string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notifications/"+notificationID+"/read", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+t.apiKey)
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// MarkAllAsRead marks all notifications as read.
|
||||
func (t *Transport) MarkAllAsRead(ctx context.Context) error {
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notifications/read-all", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+t.apiKey)
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// IsOnline checks if the API is reachable.
|
||||
func (t *Transport) IsOnline(ctx context.Context) bool {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"/health", nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode == http.StatusOK
|
||||
}
|
||||
|
||||
// Close closes the transport.
|
||||
func (t *Transport) Close() {
|
||||
t.httpClient.CloseIdleConnections()
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Package ironnotify provides an SDK for sending notifications via IronNotify.
|
||||
package ironnotify
|
||||
|
||||
import "time"
|
||||
|
||||
// SeverityLevel represents the severity of a notification.
|
||||
type SeverityLevel string
|
||||
|
||||
const (
|
||||
SeverityInfo SeverityLevel = "info"
|
||||
SeveritySuccess SeverityLevel = "success"
|
||||
SeverityWarning SeverityLevel = "warning"
|
||||
SeverityError SeverityLevel = "error"
|
||||
SeverityCritical SeverityLevel = "critical"
|
||||
)
|
||||
|
||||
// ConnectionState represents the WebSocket connection state.
|
||||
type ConnectionState string
|
||||
|
||||
const (
|
||||
StateDisconnected ConnectionState = "disconnected"
|
||||
StateConnecting ConnectionState = "connecting"
|
||||
StateConnected ConnectionState = "connected"
|
||||
StateReconnecting ConnectionState = "reconnecting"
|
||||
)
|
||||
|
||||
// NotificationAction represents an action button on a notification.
|
||||
type NotificationAction struct {
|
||||
Label string `json:"label"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationPayload represents the data sent to create a notification.
|
||||
type NotificationPayload struct {
|
||||
EventType string `json:"eventType"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Severity SeverityLevel `json:"severity,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Actions []NotificationAction `json:"actions,omitempty"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
GroupKey string `json:"groupKey,omitempty"`
|
||||
DeduplicationKey string `json:"deduplicationKey,omitempty"`
|
||||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
// Notification represents a notification received from the server.
|
||||
type Notification struct {
|
||||
ID string `json:"id"`
|
||||
EventType string `json:"eventType"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Severity SeverityLevel `json:"severity"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Actions []NotificationAction `json:"actions,omitempty"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
GroupKey string `json:"groupKey,omitempty"`
|
||||
Read bool `json:"read"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
// SendResult represents the result of sending a notification.
|
||||
type SendResult struct {
|
||||
Success bool `json:"success"`
|
||||
NotificationID string `json:"notificationId,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Queued bool `json:"queued,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationHandler is a callback function for receiving notifications.
|
||||
type NotificationHandler func(notification Notification)
|
||||
|
||||
// UnreadCountHandler is a callback function for unread count changes.
|
||||
type UnreadCountHandler func(count int)
|
||||
|
||||
// ConnectionStateHandler is a callback function for connection state changes.
|
||||
type ConnectionStateHandler func(state ConnectionState)
|
||||
Loading…
Reference in New Issue