Skip to content

Commit 512351f

Browse files
authored
Implemented SendMulticast() API and dryRun support (#258)
* Implemented SendMulticast() API and dryRun support * Cleaned up the tests * Added integration test * Cleaned up docs; Added test case * Fixed a typo
1 parent ab1fb5c commit 512351f

File tree

4 files changed

+518
-51
lines changed

4 files changed

+518
-51
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Unreleased
22

3+
- [added] Implemented `messaging.MulticastMessage` type and the
4+
`messaging.SendMulticast()` function for sending the same
5+
message to multiple recipients.
6+
- [added] Implemented `messaging.SendAllDryRun()` and
7+
`messaging.SendMulticastDryRun()` functions for sending messages
8+
in the validate only mode.
39
- [added] Implemented `messaging.SendAll()` function for sending
410
up to 100 FCM messages at a time.
511

integration/messaging/messaging_test.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func TestSendAll(t *testing.T) {
130130
},
131131
}
132132

133-
br, err := client.SendAll(context.Background(), messages)
133+
br, err := client.SendAllDryRun(context.Background(), messages)
134134
if err != nil {
135135
t.Fatal(err)
136136
}
@@ -173,7 +173,7 @@ func TestSendHundred(t *testing.T) {
173173
messages = append(messages, m)
174174
}
175175

176-
br, err := client.SendAll(context.Background(), messages)
176+
br, err := client.SendAllDryRun(context.Background(), messages)
177177
if err != nil {
178178
t.Fatal(err)
179179
}
@@ -196,6 +196,38 @@ func TestSendHundred(t *testing.T) {
196196
}
197197
}
198198

199+
func TestSendMulticast(t *testing.T) {
200+
message := &messaging.MulticastMessage{
201+
Notification: &messaging.Notification{
202+
Title: "title",
203+
Body: "body",
204+
},
205+
Tokens: []string{"INVALID_TOKEN", "ANOTHER_INVALID_TOKEN"},
206+
}
207+
208+
br, err := client.SendMulticastDryRun(context.Background(), message)
209+
if err != nil {
210+
t.Fatal(err)
211+
}
212+
213+
if len(br.Responses) != 2 {
214+
t.Errorf("len(Responses) = %d; want = 2", len(br.Responses))
215+
}
216+
if br.SuccessCount != 0 {
217+
t.Errorf("SuccessCount = %d; want = 0", br.SuccessCount)
218+
}
219+
if br.FailureCount != 2 {
220+
t.Errorf("FailureCount = %d; want = 2", br.FailureCount)
221+
}
222+
223+
for i := 0; i < 2; i++ {
224+
sr := br.Responses[i]
225+
if err := checkErrorSendResponse(sr); err != nil {
226+
t.Errorf("Responses[%d]: %v", i, err)
227+
}
228+
}
229+
}
230+
199231
func TestSubscribe(t *testing.T) {
200232
tmr, err := client.SubscribeToTopic(context.Background(), []string{testRegistrationToken}, "mock-topic")
201233
if err != nil {
@@ -229,3 +261,17 @@ func checkSuccessfulSendResponse(sr *messaging.SendResponse) error {
229261

230262
return nil
231263
}
264+
265+
func checkErrorSendResponse(sr *messaging.SendResponse) error {
266+
if sr.Success {
267+
return fmt.Errorf("Success = true; want = false")
268+
}
269+
if sr.MessageID != "" {
270+
return fmt.Errorf("MessageID = %q; want = %q", sr.MessageID, "")
271+
}
272+
if sr.Error == nil || !messaging.IsInvalidArgument(sr.Error) {
273+
return fmt.Errorf("Error = %v; want = InvalidArgumentError", sr.Error)
274+
}
275+
276+
return nil
277+
}

messaging/messaging_batch.go

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,47 @@ import (
3131
"firebase.google.com/go/internal"
3232
)
3333

34+
const maxMessages = 100
3435
const multipartBoundary = "__END_OF_PART__"
3536

37+
// MulticastMessage represents a message that can be sent to multiple devices via Firebase Cloud
38+
// Messaging (FCM).
39+
//
40+
// It contains payload information as well as the list of device registration tokens to which the
41+
// message should be sent. A single MulticastMessage may contain up to 100 registration tokens.
42+
type MulticastMessage struct {
43+
Tokens []string
44+
Data map[string]string
45+
Notification *Notification
46+
Android *AndroidConfig
47+
Webpush *WebpushConfig
48+
APNS *APNSConfig
49+
}
50+
51+
func (mm *MulticastMessage) toMessages() ([]*Message, error) {
52+
if len(mm.Tokens) == 0 {
53+
return nil, errors.New("tokens must not be nil or empty")
54+
}
55+
if len(mm.Tokens) > maxMessages {
56+
return nil, fmt.Errorf("tokens must not contain more than %d elements", maxMessages)
57+
}
58+
59+
var messages []*Message
60+
for _, token := range mm.Tokens {
61+
temp := &Message{
62+
Token: token,
63+
Data: mm.Data,
64+
Notification: mm.Notification,
65+
Android: mm.Android,
66+
Webpush: mm.Webpush,
67+
APNS: mm.APNS,
68+
}
69+
messages = append(messages, temp)
70+
}
71+
72+
return messages, nil
73+
}
74+
3675
// SendResponse represents the status of an individual message that was sent as part of a batch
3776
// request.
3877
type SendResponse struct {
@@ -48,27 +87,87 @@ type BatchResponse struct {
4887
Responses []*SendResponse
4988
}
5089

51-
// SendAll sends all the messages in the given array via Firebase Cloud Messaging.
90+
// SendAll sends the messages in the given array via Firebase Cloud Messaging.
5291
//
5392
// The messages array may contain up to 100 messages. SendAll employs batching to send the entire
5493
// array of mssages as a single RPC call. Compared to the `Send()` function,
55-
// this is a significantly more efficient way to send multiple messages. The responses
56-
// list obtained from the return value corresponds to the order of input messages. An error from
94+
// this is a significantly more efficient way to send multiple messages. The responses list
95+
// obtained from the return value corresponds to the order of the input messages. An error from
5796
// SendAll indicates a total failure -- i.e. none of the messages in the array could be sent.
5897
// Partial failures are indicated by a `BatchResponse` return value.
5998
func (c *Client) SendAll(ctx context.Context, messages []*Message) (*BatchResponse, error) {
6099
return c.sendBatch(ctx, messages, false)
61100
}
62101

102+
// SendAllDryRun sends the messages in the given array via Firebase Cloud Messaging in the
103+
// dry run (validation only) mode.
104+
//
105+
// This function does not actually deliver any messages to target devices. Instead, it performs all
106+
// the SDK-level and backend validations on the messages, and emulates the send operation.
107+
//
108+
// The messages array may contain up to 100 messages. SendAllDryRun employs batching to send the
109+
// entire array of mssages as a single RPC call. Compared to the `SendDryRun()` function, this
110+
// is a significantly more efficient way to validate sending multiple messages. The responses list
111+
// obtained from the return value corresponds to the order of the input messages. An error from
112+
// SendAllDryRun indicates a total failure -- i.e. none of the messages in the array could be sent
113+
// for validation. Partial failures are indicated by a `BatchResponse` return value.
114+
func (c *Client) SendAllDryRun(ctx context.Context, messages []*Message) (*BatchResponse, error) {
115+
return c.sendBatch(ctx, messages, true)
116+
}
117+
118+
// SendMulticast sends the given multicast message to all the FCM registration tokens specified.
119+
//
120+
// The tokens array in MulticastMessage may contain up to 100 tokens. SendMulticast uses the
121+
// `SendAll()` function to send the given message to all the target recipients. The
122+
// responses list obtained from the return value corresponds to the order of the input tokens. An
123+
// error from SendMulticast indicates a total failure -- i.e. the message could not be sent to any
124+
// of the recipients. Partial failures are indicated by a `BatchResponse` return value.
125+
func (c *Client) SendMulticast(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
126+
messages, err := toMessages(message)
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
return c.SendAll(ctx, messages)
132+
}
133+
134+
// SendMulticastDryRun sends the given multicast message to all the specified FCM registration
135+
// tokens in the dry run (validation only) mode.
136+
//
137+
// This function does not actually deliver any messages to target devices. Instead, it performs all
138+
// the SDK-level and backend validations on the messages, and emulates the send operation.
139+
//
140+
// The tokens array in MulticastMessage may contain up to 100 tokens. SendMulticastDryRun uses the
141+
// `SendAllDryRun()` function to send the given message. The responses list obtained from
142+
// the return value corresponds to the order of the input tokens. An error from SendMulticastDryRun
143+
// indicates a total failure -- i.e. none of the messages were sent to FCM for validation. Partial
144+
// failures are indicated by a `BatchResponse` return value.
145+
func (c *Client) SendMulticastDryRun(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
146+
messages, err := toMessages(message)
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
return c.SendAllDryRun(ctx, messages)
152+
}
153+
154+
func toMessages(message *MulticastMessage) ([]*Message, error) {
155+
if message == nil {
156+
return nil, errors.New("message must not be nil")
157+
}
158+
159+
return message.toMessages()
160+
}
161+
63162
func (c *Client) sendBatch(
64163
ctx context.Context, messages []*Message, dryRun bool) (*BatchResponse, error) {
65164

66165
if len(messages) == 0 {
67166
return nil, errors.New("messages must not be nil or empty")
68167
}
69168

70-
if len(messages) > 100 {
71-
return nil, errors.New("messages must not contain more than 100 elements")
169+
if len(messages) > maxMessages {
170+
return nil, fmt.Errorf("messages must not contain more than %d elements", maxMessages)
72171
}
73172

74173
request, err := c.newBatchRequest(messages, dryRun)

0 commit comments

Comments
 (0)