@@ -28,14 +28,12 @@ import (
2828 "time"
2929
3030 "firebase.google.com/go/internal"
31+ "google.golang.org/api/transport"
3132)
3233
3334const (
3435 messagingEndpoint = "https://fcm.googleapis.com/v1"
3536 batchEndpoint = "https://fcm.googleapis.com/batch"
36- iidEndpoint = "https://iid.googleapis.com"
37- iidSubscribe = "iid/v1:batchAdd"
38- iidUnsubscribe = "iid/v1:batchRemove"
3937
4038 firebaseClientHeader = "X-Firebase-Client"
4139 apiFormatVersionHeader = "X-GOOG-API-FORMAT-VERSION"
@@ -104,37 +102,8 @@ var (
104102 "app instance has been unregistered; code: " + registrationTokenNotRegistered ,
105103 },
106104 }
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- }
126105)
127106
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-
138107// Message to be sent via Firebase Cloud Messaging.
139108//
140109// Message contains payload data, recipient information and platform-specific configuration
@@ -635,39 +604,10 @@ type ErrorInfo struct {
635604 Reason string
636605}
637606
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
671611}
672612
673613// 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
679619 return nil , errors .New ("project ID is required to access Firebase Cloud Messaging client" )
680620 }
681621
682- hc , _ , err := internal .NewHTTPClient (ctx , c .Opts ... )
622+ hc , _ , err := transport .NewHTTPClient (ctx , c .Opts ... )
683623 if err != nil {
684624 return nil , err
685625 }
686626
687627 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 {
688653 fcmEndpoint : messagingEndpoint ,
689654 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+ }
695659}
696660
697661// Send sends a Message to Firebase Cloud Messaging.
698662//
699663// The Message must specify exactly one of Token, Topic and Condition fields. FCM will
700664// customize the message for each target platform based on the arguments specified in the
701665// 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 ) {
703667 payload := & fcmRequest {
704668 Message : message ,
705669 }
@@ -710,36 +674,28 @@ func (c *Client) Send(ctx context.Context, message *Message) (string, error) {
710674//
711675// This function does not actually deliver the message to target devices. Instead, it performs all
712676// 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 ) {
714678 payload := & fcmRequest {
715679 ValidateOnly : true ,
716680 Message : message ,
717681 }
718682 return c .makeSendRequest (ctx , payload )
719683}
720684
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
729688 }
730- return c .makeTopicManagementRequest (ctx , req )
731- }
732689
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 ),
741694 }
742- return c .makeTopicManagementRequest (ctx , req )
695+
696+ var result fcmResponse
697+ _ , err := c .httpClient .DoAndUnmarshal (ctx , request , & result )
698+ return result .Name , err
743699}
744700
745701// IsInternal checks if the given error was due to an internal server error.
@@ -812,49 +768,6 @@ type fcmError struct {
812768 } `json:"error"`
813769}
814770
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-
858771func handleFCMError (resp * internal.Response ) error {
859772 var fe fcmError
860773 json .Unmarshal (resp .Body , & fe ) // ignore any json parse errors at this level
@@ -882,59 +795,3 @@ func handleFCMError(resp *internal.Response) error {
882795 }
883796 return internal .Errorf (clientCode , "http error status: %d; reason: %s" , resp .Status , msg )
884797}
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