forked from nikoksr/notify
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfcm.go
130 lines (107 loc) · 3.31 KB
/
fcm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package fcm
import (
"context"
"errors"
"fmt"
"net/http"
"firebase.google.com/go/v4/messaging"
"github.com/appleboy/go-fcm"
"github.com/nikoksr/notify"
)
// Compile-time check that Service satisfies the Notifier interface.
var _ notify.Notifier = (*Service)(nil)
// Used to generate mocks for the FCM client.
type fcmClient interface {
Send(ctx context.Context, message ...*messaging.Message) (*messaging.BatchResponse, error)
SendMulticast(ctx context.Context, message *messaging.MulticastMessage) (*messaging.BatchResponse, error)
}
// Service encapsulates the FCM client along with internal state for storing device tokens.
type Service struct {
client fcmClient
deviceTokens []string
}
// Option is a function that configures a Service.
type Option func(*Service) error
// WithCredentialsFile returns an Option to configure the FCM client with a credentials file.
func WithCredentialsFile(filename string) Option {
return func(s *Service) error {
client, ok := s.client.(*fcm.Client)
if !ok {
return errors.New("client is not of type *fcm.Client")
}
return fcm.WithCredentialsFile(filename)(client)
}
}
// WithProjectID returns an Option to configure the FCM client with a project ID.
func WithProjectID(projectID string) Option {
return func(s *Service) error {
client, ok := s.client.(*fcm.Client)
if !ok {
return errors.New("client is not of type *fcm.Client")
}
return fcm.WithProjectID(projectID)(client)
}
}
// WithHTTPClient returns an Option to configure the FCM client with a custom HTTP client.
func WithHTTPClient(httpClient *http.Client) Option {
return func(s *Service) error {
client, ok := s.client.(*fcm.Client)
if !ok {
return errors.New("client is not of type *fcm.Client")
}
return fcm.WithHTTPClient(httpClient)(client)
}
}
// New returns a new instance of a FCM notification service.
func New(ctx context.Context, opts ...Option) (*Service, error) {
client, err := fcm.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("create FCM client: %w", err)
}
s := &Service{
client: client,
deviceTokens: []string{},
}
for _, opt := range opts {
if err = opt(s); err != nil {
return nil, fmt.Errorf("apply option: %w", err)
}
}
return s, nil
}
// AddReceivers takes FCM device tokens and appends them to the internal device tokens slice.
func (s *Service) AddReceivers(deviceTokens ...string) {
s.deviceTokens = append(s.deviceTokens, deviceTokens...)
}
// Send takes a message subject and a message body and sends them to all previously set devices.
func (s *Service) Send(ctx context.Context, subject, message string) error {
if len(s.deviceTokens) == 0 {
return errors.New("no device tokens set")
}
if len(s.deviceTokens) == 1 {
msg := &messaging.Message{
Token: s.deviceTokens[0],
Notification: &messaging.Notification{
Title: subject,
Body: message,
},
}
_, err := s.client.Send(ctx, msg)
if err != nil {
return fmt.Errorf("send message to FCM device with token %q: %w", s.deviceTokens[0], err)
}
} else {
msg := &messaging.MulticastMessage{
Tokens: s.deviceTokens,
Notification: &messaging.Notification{
Title: subject,
Body: message,
},
}
_, err := s.client.SendMulticast(ctx, msg)
if err != nil {
return fmt.Errorf("send multicast message to FCM devices: %w", err)
}
}
return nil
}