Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions integration/messaging/messaging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,130 @@ func TestSendInvalidToken(t *testing.T) {
}
}

func TestSendEach(t *testing.T) {
messages := []*messaging.Message{
{
Notification: &messaging.Notification{
Title: "Title 1",
Body: "Body 1",
},
Topic: "foo-bar",
},
{
Notification: &messaging.Notification{
Title: "Title 2",
Body: "Body 2",
},
Topic: "foo-bar",
},
{
Notification: &messaging.Notification{
Title: "Title 3",
Body: "Body 3",
},
Token: "INVALID_TOKEN",
},
}

br, err := client.SendEachDryRun(context.Background(), messages)
if err != nil {
t.Fatal(err)
}

if len(br.Responses) != 3 {
t.Errorf("len(Responses) = %d; want = 3", len(br.Responses))
}
if br.SuccessCount != 2 {
t.Errorf("SuccessCount = %d; want = 2", br.SuccessCount)
}
if br.FailureCount != 1 {
t.Errorf("FailureCount = %d; want = 1", br.FailureCount)
}

for i := 0; i < 2; i++ {
sr := br.Responses[i]
if err := checkSuccessfulSendResponse(sr); err != nil {
t.Errorf("Responses[%d]: %v", i, err)
}
}

sr := br.Responses[2]
if sr.Success {
t.Errorf("Responses[2]: Success = true; want = false")
}
if sr.MessageID != "" {
t.Errorf("Responses[2]: MessageID = %q; want = %q", sr.MessageID, "")
}
if sr.Error == nil || !messaging.IsInvalidArgument(sr.Error) {
t.Errorf("Responses[2]: Error = %v; want = InvalidArgumentError", sr.Error)
}
}

func TestSendEachFiveHundred(t *testing.T) {
var messages []*messaging.Message
const limit = 500
for i := 0; i < limit; i++ {
m := &messaging.Message{
Topic: fmt.Sprintf("foo-bar-%d", i%10),
}
messages = append(messages, m)
}

br, err := client.SendEachDryRun(context.Background(), messages)
if err != nil {
t.Fatal(err)
}

if len(br.Responses) != limit {
t.Errorf("len(Responses) = %d; want = %d", len(br.Responses), limit)
}
if br.SuccessCount != limit {
t.Errorf("SuccessCount = %d; want = %d", br.SuccessCount, limit)
}
if br.FailureCount != 0 {
t.Errorf("FailureCount = %d; want = 0", br.FailureCount)
}

for i := 0; i < limit; i++ {
sr := br.Responses[i]
if err := checkSuccessfulSendResponse(sr); err != nil {
t.Errorf("Responses[%d]: %v", i, err)
}
}
}

func TestSendEachForMulticast(t *testing.T) {
message := &messaging.MulticastMessage{
Notification: &messaging.Notification{
Title: "title",
Body: "body",
},
Tokens: []string{"INVALID_TOKEN", "ANOTHER_INVALID_TOKEN"},
}

br, err := client.SendEachForMulticastDryRun(context.Background(), message)
if err != nil {
t.Fatal(err)
}

if len(br.Responses) != 2 {
t.Errorf("len(Responses) = %d; want = 2", len(br.Responses))
}
if br.SuccessCount != 0 {
t.Errorf("SuccessCount = %d; want = 0", br.SuccessCount)
}
if br.FailureCount != 2 {
t.Errorf("FailureCount = %d; want = 2", br.FailureCount)
}

for i := 0; i < 2; i++ {
sr := br.Responses[i]
if err := checkErrorSendResponse(sr); err != nil {
t.Errorf("Responses[%d]: %v", i, err)
}
}
}

func TestSendAll(t *testing.T) {
messages := []*messaging.Message{
{
Expand Down
151 changes: 141 additions & 10 deletions messaging/messaging_batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"mime/multipart"
"net/http"
"net/textproto"
"sync"

"firebase.google.com/go/v4/internal"
)
Expand Down Expand Up @@ -87,14 +88,138 @@ type BatchResponse struct {
Responses []*SendResponse
}

// SendEach sends the messages in the given array via Firebase Cloud Messaging.
//
// The messages array may contain up to 500 messages. Unlike `SendAll()`, SendEach sends the entire
// array of messages by making a single HTTP call for each message. The responses list
// obtained from the return value corresponds to the order of the input messages. An error
// from SendEach or a `BatchResponse` with all failures indicates a total failure, meaning that
// none of the messages in the list could be sent. Partial failures or no failures are only
// indicated by a `BatchResponse` return value.
func (c *fcmClient) SendEach(ctx context.Context, messages []*Message) (*BatchResponse, error) {
return c.sendEachInBatch(ctx, messages, false)
}

// SendEachDryRun sends the messages in the given array via Firebase Cloud Messaging in the
// dry run (validation only) mode.
//
// This function does not actually deliver any messages to target devices. Instead, it performs all
// the SDK-level and backend validations on the messages, and emulates the send operation.
//
// The messages array may contain up to 500 messages. Unlike `SendAllDryRun()`, SendEachDryRun sends
// the entire array of messages by making a single HTTP call for each message. The responses list
// obtained from the return value corresponds to the order of the input messages. An error
// from SendEachDryRun or a `BatchResponse` with all failures indicates a total failure, meaning
// that none of the messages in the list could be sent. Partial failures or no failures are only
// indicated by a `BatchResponse` return value.
func (c *fcmClient) SendEachDryRun(ctx context.Context, messages []*Message) (*BatchResponse, error) {
return c.sendEachInBatch(ctx, messages, true)
}

// SendMulticast sends the given multicast message to all the FCM registration tokens specified.
//
// The tokens array in MulticastMessage may contain up to 500 tokens. SendMulticast uses the
// `SendEach()` function to send the given message to all the target recipients. The
// responses list obtained from the return value corresponds to the order of the input tokens. An error
// from SendEachForMulticast or a `BatchResponse` with all failures indicates a total failure, meaning
// that none of the messages in the list could be sent. Partial failures or no failures are only
// indicated by a `BatchResponse` return value.
func (c *fcmClient) SendEachForMulticast(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
messages, err := toMessages(message)
if err != nil {
return nil, err
}

return c.SendEach(ctx, messages)
}

// SendEachForMulticastDryRun sends the given multicast message to all the specified FCM registration
// tokens in the dry run (validation only) mode.
//
// This function does not actually deliver any messages to target devices. Instead, it performs all
// the SDK-level and backend validations on the messages, and emulates the send operation.
//
// The tokens array in MulticastMessage may contain up to 500 tokens. SendEachForMulticastDryRunn uses the
// `SendEachDryRun()` function to send the given message. The responses list obtained from
// the return value corresponds to the order of the input tokens. An error from SendEachForMulticastDryRun
// or a `BatchResponse` with all failures indicates a total failure, meaning that of the messages in the
// list could be sent. Partial failures or no failures are only
// indicated by a `BatchResponse` return value.
func (c *fcmClient) SendEachForMulticastDryRun(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
messages, err := toMessages(message)
if err != nil {
return nil, err
}

return c.SendEachDryRun(ctx, messages)
}

func (c *fcmClient) sendEachInBatch(ctx context.Context, messages []*Message, dryRun bool) (*BatchResponse, error) {
if len(messages) == 0 {
return nil, errors.New("messages must not be nil or empty")
}

if len(messages) > maxMessages {
return nil, fmt.Errorf("messages must not contain more than %d elements", maxMessages)
}

var responses []*SendResponse = make([]*SendResponse, len(messages))
var wg sync.WaitGroup

for idx, m := range messages {
if err := validateMessage(m); err != nil {
return nil, fmt.Errorf("invalid message at index %d: %v", idx, err)
}
wg.Add(1)
go func(idx int, m *Message, dryRun bool, responses []*SendResponse) {
defer wg.Done()
var resp string
var err error
if dryRun {
resp, err = c.SendDryRun(ctx, m)
} else {
resp, err = c.Send(ctx, m)
}
if err == nil {
responses[idx] = &SendResponse{
Success: true,
MessageID: resp,
}
} else {
responses[idx] = &SendResponse{
Success: false,
Error: err,
}
}
}(idx, m, dryRun, responses)
}
// Wait for all SendDryRun/Send calls to finish
wg.Wait()

successCount := 0
for _, r := range responses {
if r.Success {
successCount++
}
}

return &BatchResponse{
Responses: responses,
SuccessCount: successCount,
FailureCount: len(responses) - successCount,
}, nil
}

// SendAll sends the messages in the given array via Firebase Cloud Messaging.
//
// The messages array may contain up to 500 messages. SendAll employs batching to send the entire
// array of mssages as a single RPC call. Compared to the `Send()` function,
// array of messages as a single RPC call. Compared to the `Send()` function,
// this is a significantly more efficient way to send multiple messages. The responses list
// obtained from the return value corresponds to the order of the input messages. An error from
// SendAll indicates a total failure -- i.e. none of the messages in the array could be sent.
// Partial failures are indicated by a `BatchResponse` return value.
// SendAll indicates a total failure, meaning that none of the messages in the array could be
// sent. Partial failures are indicated by a `BatchResponse` return value.
//
// Deprecated: Use SendEach instead.
func (c *fcmClient) SendAll(ctx context.Context, messages []*Message) (*BatchResponse, error) {
return c.sendBatch(ctx, messages, false)
}
Expand All @@ -106,11 +231,13 @@ func (c *fcmClient) SendAll(ctx context.Context, messages []*Message) (*BatchRes
// the SDK-level and backend validations on the messages, and emulates the send operation.
//
// The messages array may contain up to 500 messages. SendAllDryRun employs batching to send the
// entire array of mssages as a single RPC call. Compared to the `SendDryRun()` function, this
// entire array of messages as a single RPC call. Compared to the `SendDryRun()` function, this
// is a significantly more efficient way to validate sending multiple messages. The responses list
// obtained from the return value corresponds to the order of the input messages. An error from
// SendAllDryRun indicates a total failure -- i.e. none of the messages in the array could be sent
// for validation. Partial failures are indicated by a `BatchResponse` return value.
// SendAllDryRun indicates a total failure, meaning that none of the messages in the array could
// be sent for validation. Partial failures are indicated by a `BatchResponse` return value.
//
// Deprecated: Use SendEachDryRun instead.
func (c *fcmClient) SendAllDryRun(ctx context.Context, messages []*Message) (*BatchResponse, error) {
return c.sendBatch(ctx, messages, true)
}
Expand All @@ -120,8 +247,10 @@ func (c *fcmClient) SendAllDryRun(ctx context.Context, messages []*Message) (*Ba
// The tokens array in MulticastMessage may contain up to 500 tokens. SendMulticast uses the
// `SendAll()` function to send the given message to all the target recipients. The
// responses list obtained from the return value corresponds to the order of the input tokens. An
// error from SendMulticast indicates a total failure -- i.e. the message could not be sent to any
// of the recipients. Partial failures are indicated by a `BatchResponse` return value.
// error from SendMulticast indicates a total failure, meaning that the message could not be sent
// to any of the recipients. Partial failures are indicated by a `BatchResponse` return value.
//
// Deprecated: Use SendEachForMulticast instead.
func (c *fcmClient) SendMulticast(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
messages, err := toMessages(message)
if err != nil {
Expand All @@ -140,8 +269,10 @@ func (c *fcmClient) SendMulticast(ctx context.Context, message *MulticastMessage
// The tokens array in MulticastMessage may contain up to 500 tokens. SendMulticastDryRun uses the
// `SendAllDryRun()` function to send the given message. The responses list obtained from
// the return value corresponds to the order of the input tokens. An error from SendMulticastDryRun
// indicates a total failure -- i.e. none of the messages were sent to FCM for validation. Partial
// failures are indicated by a `BatchResponse` return value.
// indicates a total failure, meaning that none of the messages were sent to FCM for validation.
// Partial failures are indicated by a `BatchResponse` return value.
//
// Deprecated: Use SendEachForMulticastDryRun instead.
func (c *fcmClient) SendMulticastDryRun(ctx context.Context, message *MulticastMessage) (*BatchResponse, error) {
messages, err := toMessages(message)
if err != nil {
Expand Down
Loading