@@ -28,14 +28,12 @@ import (
28
28
"time"
29
29
30
30
"firebase.google.com/go/internal"
31
+ "google.golang.org/api/transport"
31
32
)
32
33
33
34
const (
34
35
messagingEndpoint = "https://fcm.googleapis.com/v1"
35
36
batchEndpoint = "https://fcm.googleapis.com/batch"
36
- iidEndpoint = "https://iid.googleapis.com"
37
- iidSubscribe = "iid/v1:batchAdd"
38
- iidUnsubscribe = "iid/v1:batchRemove"
39
37
40
38
firebaseClientHeader = "X-Firebase-Client"
41
39
apiFormatVersionHeader = "X-GOOG-API-FORMAT-VERSION"
@@ -104,37 +102,8 @@ var (
104
102
"app instance has been unregistered; code: " + registrationTokenNotRegistered ,
105
103
},
106
104
}
107
-
108
- iidErrorCodes = map [string ]struct { Code , Msg string }{
109
- "INVALID_ARGUMENT" : {
110
- invalidArgument ,
111
- "request contains an invalid argument; code: " + invalidArgument ,
112
- },
113
- "NOT_FOUND" : {
114
- registrationTokenNotRegistered ,
115
- "request contains an invalid argument; code: " + registrationTokenNotRegistered ,
116
- },
117
- "INTERNAL" : {
118
- internalError ,
119
- "server encountered an internal error; code: " + internalError ,
120
- },
121
- "TOO_MANY_TOPICS" : {
122
- tooManyTopics ,
123
- "client exceeded the number of allowed topics; code: " + tooManyTopics ,
124
- },
125
- }
126
105
)
127
106
128
- // Client is the interface for the Firebase Cloud Messaging (FCM) service.
129
- type Client struct {
130
- fcmEndpoint string // to enable testing against arbitrary endpoints
131
- batchEndpoint string // to enable testing against arbitrary endpoints
132
- iidEndpoint string // to enable testing against arbitrary endpoints
133
- client * internal.HTTPClient
134
- project string
135
- version string
136
- }
137
-
138
107
// Message to be sent via Firebase Cloud Messaging.
139
108
//
140
109
// Message contains payload data, recipient information and platform-specific configuration
@@ -635,39 +604,10 @@ type ErrorInfo struct {
635
604
Reason string
636
605
}
637
606
638
- // TopicManagementResponse is the result produced by topic management operations.
639
- //
640
- // TopicManagementResponse provides an overview of how many input tokens were successfully handled,
641
- // and how many failed. In case of failures, the Errors list provides specific details concerning
642
- // each error.
643
- type TopicManagementResponse struct {
644
- SuccessCount int
645
- FailureCount int
646
- Errors []* ErrorInfo
647
- }
648
-
649
- func newTopicManagementResponse (resp * iidResponse ) * TopicManagementResponse {
650
- tmr := & TopicManagementResponse {}
651
- for idx , res := range resp .Results {
652
- if len (res ) == 0 {
653
- tmr .SuccessCount ++
654
- } else {
655
- tmr .FailureCount ++
656
- code := res ["error" ].(string )
657
- info , ok := iidErrorCodes [code ]
658
- var reason string
659
- if ok {
660
- reason = info .Msg
661
- } else {
662
- reason = unknownError
663
- }
664
- tmr .Errors = append (tmr .Errors , & ErrorInfo {
665
- Index : idx ,
666
- Reason : reason ,
667
- })
668
- }
669
- }
670
- return tmr
607
+ // Client is the interface for the Firebase Cloud Messaging (FCM) service.
608
+ type Client struct {
609
+ * fcmClient
610
+ * iidClient
671
611
}
672
612
673
613
// NewClient creates a new instance of the Firebase Cloud Messaging Client.
@@ -679,27 +619,51 @@ func NewClient(ctx context.Context, c *internal.MessagingConfig) (*Client, error
679
619
return nil , errors .New ("project ID is required to access Firebase Cloud Messaging client" )
680
620
}
681
621
682
- hc , _ , err := internal .NewHTTPClient (ctx , c .Opts ... )
622
+ hc , _ , err := transport .NewHTTPClient (ctx , c .Opts ... )
683
623
if err != nil {
684
624
return nil , err
685
625
}
686
626
687
627
return & Client {
628
+ fcmClient : newFCMClient (hc , c ),
629
+ iidClient : newIIDClient (hc ),
630
+ }, nil
631
+ }
632
+
633
+ type fcmClient struct {
634
+ fcmEndpoint string
635
+ batchEndpoint string
636
+ project string
637
+ version string
638
+ httpClient * internal.HTTPClient
639
+ }
640
+
641
+ func newFCMClient (hc * http.Client , conf * internal.MessagingConfig ) * fcmClient {
642
+ client := internal .WithDefaultRetryConfig (hc )
643
+ client .CreateErrFn = handleFCMError
644
+ client .SuccessFn = internal .HasSuccessStatus
645
+
646
+ version := fmt .Sprintf ("fire-admin-go/%s" , conf .Version )
647
+ client .Opts = []internal.HTTPOption {
648
+ internal .WithHeader (apiFormatVersionHeader , apiFormatVersion ),
649
+ internal .WithHeader (firebaseClientHeader , version ),
650
+ }
651
+
652
+ return & fcmClient {
688
653
fcmEndpoint : messagingEndpoint ,
689
654
batchEndpoint : batchEndpoint ,
690
- iidEndpoint : iidEndpoint ,
691
- client : hc ,
692
- project : c .ProjectID ,
693
- version : "fire-admin-go/" + c .Version ,
694
- }, nil
655
+ project : conf .ProjectID ,
656
+ version : version ,
657
+ httpClient : client ,
658
+ }
695
659
}
696
660
697
661
// Send sends a Message to Firebase Cloud Messaging.
698
662
//
699
663
// The Message must specify exactly one of Token, Topic and Condition fields. FCM will
700
664
// customize the message for each target platform based on the arguments specified in the
701
665
// Message.
702
- func (c * Client ) Send (ctx context.Context , message * Message ) (string , error ) {
666
+ func (c * fcmClient ) Send (ctx context.Context , message * Message ) (string , error ) {
703
667
payload := & fcmRequest {
704
668
Message : message ,
705
669
}
@@ -710,36 +674,28 @@ func (c *Client) Send(ctx context.Context, message *Message) (string, error) {
710
674
//
711
675
// This function does not actually deliver the message to target devices. Instead, it performs all
712
676
// the SDK-level and backend validations on the message, and emulates the send operation.
713
- func (c * Client ) SendDryRun (ctx context.Context , message * Message ) (string , error ) {
677
+ func (c * fcmClient ) SendDryRun (ctx context.Context , message * Message ) (string , error ) {
714
678
payload := & fcmRequest {
715
679
ValidateOnly : true ,
716
680
Message : message ,
717
681
}
718
682
return c .makeSendRequest (ctx , payload )
719
683
}
720
684
721
- // SubscribeToTopic subscribes a list of registration tokens to a topic.
722
- //
723
- // The tokens list must not be empty, and have at most 1000 tokens.
724
- func (c * Client ) SubscribeToTopic (ctx context.Context , tokens []string , topic string ) (* TopicManagementResponse , error ) {
725
- req := & iidRequest {
726
- Topic : topic ,
727
- Tokens : tokens ,
728
- op : iidSubscribe ,
685
+ func (c * fcmClient ) makeSendRequest (ctx context.Context , req * fcmRequest ) (string , error ) {
686
+ if err := validateMessage (req .Message ); err != nil {
687
+ return "" , err
729
688
}
730
- return c .makeTopicManagementRequest (ctx , req )
731
- }
732
689
733
- // UnsubscribeFromTopic unsubscribes a list of registration tokens from a topic.
734
- //
735
- // The tokens list must not be empty, and have at most 1000 tokens.
736
- func (c * Client ) UnsubscribeFromTopic (ctx context.Context , tokens []string , topic string ) (* TopicManagementResponse , error ) {
737
- req := & iidRequest {
738
- Topic : topic ,
739
- Tokens : tokens ,
740
- op : iidUnsubscribe ,
690
+ request := & internal.Request {
691
+ Method : http .MethodPost ,
692
+ URL : fmt .Sprintf ("%s/projects/%s/messages:send" , c .fcmEndpoint , c .project ),
693
+ Body : internal .NewJSONEntity (req ),
741
694
}
742
- return c .makeTopicManagementRequest (ctx , req )
695
+
696
+ var result fcmResponse
697
+ _ , err := c .httpClient .DoAndUnmarshal (ctx , request , & result )
698
+ return result .Name , err
743
699
}
744
700
745
701
// IsInternal checks if the given error was due to an internal server error.
@@ -812,49 +768,6 @@ type fcmError struct {
812
768
} `json:"error"`
813
769
}
814
770
815
- type iidRequest struct {
816
- Topic string `json:"to"`
817
- Tokens []string `json:"registration_tokens"`
818
- op string
819
- }
820
-
821
- type iidResponse struct {
822
- Results []map [string ]interface {} `json:"results"`
823
- }
824
-
825
- type iidError struct {
826
- Error string `json:"error"`
827
- }
828
-
829
- func (c * Client ) makeSendRequest (ctx context.Context , req * fcmRequest ) (string , error ) {
830
- if err := validateMessage (req .Message ); err != nil {
831
- return "" , err
832
- }
833
-
834
- request := & internal.Request {
835
- Method : http .MethodPost ,
836
- URL : fmt .Sprintf ("%s/projects/%s/messages:send" , c .fcmEndpoint , c .project ),
837
- Body : internal .NewJSONEntity (req ),
838
- Opts : []internal.HTTPOption {
839
- internal .WithHeader (apiFormatVersionHeader , apiFormatVersion ),
840
- internal .WithHeader (firebaseClientHeader , c .version ),
841
- },
842
- }
843
-
844
- resp , err := c .client .Do (ctx , request )
845
- if err != nil {
846
- return "" , err
847
- }
848
-
849
- if resp .Status == http .StatusOK {
850
- var result fcmResponse
851
- err := json .Unmarshal (resp .Body , & result )
852
- return result .Name , err
853
- }
854
-
855
- return "" , handleFCMError (resp )
856
- }
857
-
858
771
func handleFCMError (resp * internal.Response ) error {
859
772
var fe fcmError
860
773
json .Unmarshal (resp .Body , & fe ) // ignore any json parse errors at this level
@@ -882,59 +795,3 @@ func handleFCMError(resp *internal.Response) error {
882
795
}
883
796
return internal .Errorf (clientCode , "http error status: %d; reason: %s" , resp .Status , msg )
884
797
}
885
-
886
- func (c * Client ) makeTopicManagementRequest (ctx context.Context , req * iidRequest ) (* TopicManagementResponse , error ) {
887
- if len (req .Tokens ) == 0 {
888
- return nil , fmt .Errorf ("no tokens specified" )
889
- }
890
- if len (req .Tokens ) > 1000 {
891
- return nil , fmt .Errorf ("tokens list must not contain more than 1000 items" )
892
- }
893
- for _ , token := range req .Tokens {
894
- if token == "" {
895
- return nil , fmt .Errorf ("tokens list must not contain empty strings" )
896
- }
897
- }
898
-
899
- if req .Topic == "" {
900
- return nil , fmt .Errorf ("topic name not specified" )
901
- }
902
- if ! topicNamePattern .MatchString (req .Topic ) {
903
- return nil , fmt .Errorf ("invalid topic name: %q" , req .Topic )
904
- }
905
-
906
- if ! strings .HasPrefix (req .Topic , "/topics/" ) {
907
- req .Topic = "/topics/" + req .Topic
908
- }
909
-
910
- request := & internal.Request {
911
- Method : http .MethodPost ,
912
- URL : fmt .Sprintf ("%s/%s" , c .iidEndpoint , req .op ),
913
- Body : internal .NewJSONEntity (req ),
914
- Opts : []internal.HTTPOption {internal .WithHeader ("access_token_auth" , "true" )},
915
- }
916
- resp , err := c .client .Do (ctx , request )
917
- if err != nil {
918
- return nil , err
919
- }
920
-
921
- if resp .Status == http .StatusOK {
922
- var result iidResponse
923
- if err := json .Unmarshal (resp .Body , & result ); err != nil {
924
- return nil , err
925
- }
926
- return newTopicManagementResponse (& result ), nil
927
- }
928
-
929
- var ie iidError
930
- json .Unmarshal (resp .Body , & ie ) // ignore any json parse errors at this level
931
- var clientCode , msg string
932
- info , ok := iidErrorCodes [ie .Error ]
933
- if ok {
934
- clientCode , msg = info .Code , info .Msg
935
- } else {
936
- clientCode = unknownError
937
- msg = fmt .Sprintf ("client encountered an unknown error; response: %s" , string (resp .Body ))
938
- }
939
- return nil , internal .Errorf (clientCode , "http error status: %d; reason: %s" , resp .Status , msg )
940
- }
0 commit comments