├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _examples ├── high_priority │ └── high_priority_notification.go ├── normal_priority │ └── normal_priority_notification.go └── sound │ └── ios_notification_with_sound.go ├── doc.go ├── fcm.go ├── fcm_test.go ├── models.go └── models_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x 5 | - 1.6 6 | - 1.7.x 7 | - master 8 | 9 | sudo: false 10 | 11 | before_install: 12 | - go get github.com/mattn/goveralls 13 | script: 14 | - $HOME/gopath/bin/goveralls -ignore=./examples/sound/ios_notification_with_sound.go,./examples/normal_priority/normal_priority_notification.go,./examples/high_priority/high_priority_notification.go -service=travis-ci 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mad Devs Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase Cloud Notifications Client 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/maddevsio/fcm)](https://goreportcard.com/report/github.com/maddevsio/fcm) 4 | [![Build Status](https://travis-ci.org/maddevsio/fcm.svg)](https://travis-ci.org/maddevsio/fcm.svg)[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) 5 | [![](https://godoc.org/github.com/maddevsio/fcm?status.svg)](https://godoc.org/github.com/maddevsio/fcm) 6 | [![Coverage Status](https://coveralls.io/repos/github/maddevsio/fcm/badge.svg?branch=master)](https://coveralls.io/github/maddevsio/fcm?branch=master) 7 | 8 | Firebase Cloud Messaging for application servers implemented using the Go programming language. 9 | It's designed for simple push notification sending via HTTP API 10 | 11 | # Getting started 12 | 13 | To install fcm, use go get: 14 | 15 | ``` 16 | go get gopkg.in/maddevsio/fcm.v1 17 | ``` 18 | 19 | Import fcm with the following: 20 | 21 | ``` 22 | import "gopkg.in/maddevsio/fcm.v1" 23 | ``` 24 | 25 | # Sample usage 26 | 27 | ``` 28 | package main 29 | 30 | import ( 31 | "fmt" 32 | "log" 33 | 34 | "gopkg.in/maddevsio/fcm.v1" 35 | ) 36 | 37 | func main() { 38 | data := map[string]string{ 39 | "msg": "Hello World1", 40 | "sum": "Happy Day", 41 | } 42 | c := fcm.NewFCM("serverKey") 43 | token := "token" 44 | response, err := c.Send(fcm.Message{ 45 | Data: data, 46 | RegistrationIDs: []string{token}, 47 | ContentAvailable: true, 48 | Priority: fcm.PriorityHigh, 49 | Notification: fcm.Notification{ 50 | Title: "Hello", 51 | Body: "World", 52 | }, 53 | }) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | fmt.Println("Status Code :", response.StatusCode) 58 | fmt.Println("Success :", response.Success) 59 | fmt.Println("Fail :", response.Fail) 60 | fmt.Println("Canonical_ids :", response.CanonicalIDs) 61 | fmt.Println("Topic MsgId :", response.MsgID) 62 | } 63 | 64 | ``` 65 | 66 | More examples can be found in /_examples/ directory 67 | 68 | # License 69 | 70 | MIT License 71 | 72 | Copyright (c) 2017 Mad Devs Developers 73 | 74 | Permission is hereby granted, free of charge, to any person obtaining a copy 75 | of this software and associated documentation files (the "Software"), to deal 76 | in the Software without restriction, including without limitation the rights 77 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 78 | copies of the Software, and to permit persons to whom the Software is 79 | furnished to do so, subject to the following conditions: 80 | 81 | The above copyright notice and this permission notice shall be included in all 82 | copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 85 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 86 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 87 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 88 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 89 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 90 | SOFTWARE. 91 | -------------------------------------------------------------------------------- /_examples/high_priority/high_priority_notification.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/maddevsio/fcm" 8 | ) 9 | 10 | func main() { 11 | data := map[string]string{ 12 | "first": "World", 13 | "second": "Hello", 14 | } 15 | c := fcm.NewFCM("serverKey") 16 | token := "token" 17 | response, err := c.Send(&fcm.Message{ 18 | Data: data, 19 | RegistrationIDs: []string{token}, 20 | ContentAvailable: true, 21 | Priority: fcm.PriorityHigh, 22 | Notification: &fcm.Notification{ 23 | Title: "Hello", 24 | Body: "World", 25 | }, 26 | }) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | fmt.Println("Status Code :", response.StatusCode) 31 | fmt.Println("Success :", response.Success) 32 | fmt.Println("Fail :", response.Fail) 33 | fmt.Println("Canonical_ids :", response.CanonicalIDs) 34 | fmt.Println("Topic MsgId :", response.MsgID) 35 | } 36 | -------------------------------------------------------------------------------- /_examples/normal_priority/normal_priority_notification.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/maddevsio/fcm" 8 | ) 9 | 10 | func main() { 11 | data := map[string]string{ 12 | "msg": "Hello World1", 13 | "sum": "Happy Day", 14 | } 15 | c := fcm.NewFCM("serverKey") 16 | token := "token" 17 | response, err := c.Send(&fcm.Message{ 18 | Data: data, 19 | RegistrationIDs: []string{token}, 20 | ContentAvailable: true, 21 | Priority: fcm.PriorityNormal, 22 | Notification: &fcm.Notification{ 23 | Title: "Hello", 24 | Body: "World", 25 | }, 26 | }) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | fmt.Println("Status Code :", response.StatusCode) 31 | fmt.Println("Success :", response.Success) 32 | fmt.Println("Fail :", response.Fail) 33 | fmt.Println("Canonical_ids :", response.CanonicalIDs) 34 | fmt.Println("Topic MsgId :", response.MsgID) 35 | } 36 | -------------------------------------------------------------------------------- /_examples/sound/ios_notification_with_sound.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/maddevsio/fcm" 8 | ) 9 | 10 | func main() { 11 | data := map[string]string{ 12 | "first": "World", 13 | "second": "Hello", 14 | } 15 | c := fcm.NewFCM("serverKey") 16 | token := "token" 17 | response, err := c.Send(&fcm.Message{ 18 | Data: data, 19 | RegistrationIDs: []string{token}, 20 | ContentAvailable: true, 21 | Priority: fcm.PriorityNormal, 22 | Notification: &fcm.Notification{ 23 | Title: "Hello", 24 | Body: "World", 25 | Sound: "default", 26 | }, 27 | }) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Println("Status Code :", response.StatusCode) 32 | fmt.Println("Success :", response.Success) 33 | fmt.Println("Fail :", response.Fail) 34 | fmt.Println("Canonical_ids :", response.CanonicalIDs) 35 | fmt.Println("Topic MsgId :", response.MsgID) 36 | } 37 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package firebase provides integration with Firebase Cloud Notification HTTP API https://firebase.google.com/docs/cloud-messaging/http-server-ref 3 | You can send push notifications to Android and iOS devices via simple API. 4 | 5 | Example: 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | 12 | "github.com/maddevsio/fcm" 13 | ) 14 | 15 | func main() { 16 | data := map[string]string{ 17 | "msg": "Hello World1", 18 | "sum": "Happy Day", 19 | } 20 | c := fcm.NewFCM("serverKey") 21 | token := "token" 22 | response, err := c.Send(&fcm.Message{ 23 | Data: data, 24 | RegistrationIDs: []string{token}, 25 | ContentAvailable: true, 26 | Priority: fcm.PriorityHigh, 27 | Notification: &fcm.Notification{ 28 | Title: "Hello", 29 | Body: "World", 30 | }, 31 | }) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | fmt.Println("Status Code :", response.StatusCode) 36 | fmt.Println("Success :", response.Success) 37 | fmt.Println("Fail :", response.Fail) 38 | fmt.Println("Canonical_ids :", response.CanonicalIDs) 39 | fmt.Println("Topic MsgId :", response.MsgID) 40 | } 41 | 42 | 43 | 44 | If you want to send notification with Sound or Badge, then use: 45 | response, err := c.Send(&fcm.Message{ 46 | Notification: &fcm.Notification{ 47 | Title: "Hello", 48 | Body: "World", 49 | Sound: "default", 50 | Badge: "3", 51 | }, 52 | }) 53 | 54 | */ 55 | package fcm 56 | -------------------------------------------------------------------------------- /fcm.go: -------------------------------------------------------------------------------- 1 | package fcm 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | const ( 12 | // PriorityHigh used for high notification priority 13 | PriorityHigh = "high" 14 | 15 | // PriorityNormal used for normal notification priority 16 | PriorityNormal = "normal" 17 | 18 | // HeaderRetryAfter HTTP header constant 19 | HeaderRetryAfter = "Retry-After" 20 | 21 | // ErrorKey readable error caching 22 | ErrorKey = "error" 23 | 24 | // MethodPOST indicates http post method 25 | MethodPOST = "POST" 26 | ) 27 | 28 | var ( 29 | // retryableErrors whether the error is a retryable 30 | retryableErrors = map[string]bool{ 31 | "Unavailable": true, 32 | "InternalServerError": true, 33 | } 34 | 35 | // fcmServerUrl for testing purposes 36 | FCMServerURL = "https://fcm.googleapis.com/fcm/send" 37 | ) 38 | 39 | // FCM stores client with api key to firebase 40 | type FCM struct { 41 | APIKey string 42 | HttpClient *http.Client 43 | } 44 | 45 | // NewFCM creates a new client 46 | func NewFCM(apiKey string) *FCM { 47 | return &FCM{ 48 | APIKey: apiKey, 49 | HttpClient: &http.Client{}, 50 | } 51 | } 52 | 53 | // NewFCM creates a new client 54 | func NewFCMWithClient(apiKey string, httpClient *http.Client) *FCM { 55 | return &FCM{ 56 | APIKey: apiKey, 57 | HttpClient: httpClient, 58 | } 59 | } 60 | 61 | func (f *FCM) AuthorizationToken() string { 62 | return fmt.Sprintf("key=%v", f.APIKey) 63 | } 64 | 65 | // Send message to FCM 66 | func (f *FCM) Send(message Message) (Response, error) { 67 | 68 | data, err := json.Marshal(message) 69 | if err != nil { 70 | return Response{}, err 71 | } 72 | 73 | req, err := http.NewRequest(MethodPOST, FCMServerURL, bytes.NewBuffer(data)) 74 | if err != nil { 75 | return Response{}, err 76 | } 77 | 78 | req.Header.Set("Authorization", f.AuthorizationToken()) 79 | req.Header.Set("Content-Type", "application/json") 80 | 81 | resp, err := f.HttpClient.Do(req) 82 | if err != nil { 83 | return Response{}, err 84 | } 85 | defer resp.Body.Close() 86 | 87 | response := Response{StatusCode: resp.StatusCode} 88 | if resp.StatusCode == 200 || (resp.StatusCode >= 500 && resp.StatusCode < 600) { 89 | response.RetryAfter = resp.Header.Get(HeaderRetryAfter) 90 | } 91 | 92 | if resp.StatusCode != 200 { 93 | return response, fmt.Errorf("%d status code", resp.StatusCode) 94 | } 95 | 96 | body, err := ioutil.ReadAll(resp.Body) 97 | if err != nil { 98 | return response, err 99 | } 100 | 101 | if err := json.Unmarshal(body, &response); err != nil { 102 | return response, err 103 | } 104 | 105 | if err := f.Failed(&response); err != nil { 106 | return response, err 107 | } 108 | response.Ok = true 109 | 110 | return response, nil 111 | } 112 | 113 | // Failed method indicates if the server couldn't process 114 | // the request in time. 115 | func (f *FCM) Failed(response *Response) error { 116 | for _, response := range response.Results { 117 | if retryableErrors[response.Error] { 118 | return fmt.Errorf("Failed %s", response.Error) 119 | } 120 | } 121 | 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /fcm_test.go: -------------------------------------------------------------------------------- 1 | package fcm 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestSendTopic(t *testing.T) { 11 | srv := httptest.NewServer(http.HandlerFunc(handleTopic)) 12 | FCMServerURL = srv.URL 13 | 14 | defer srv.Close() 15 | 16 | c := NewFCM("key") 17 | 18 | data := map[string]string{ 19 | "first": "value", 20 | "second": "value", 21 | } 22 | 23 | res, err := c.Send(Message{ 24 | Data: data, 25 | To: "/topics/topicName", 26 | }) 27 | if err != nil { 28 | t.Error("Response Error : ", err) 29 | } 30 | 31 | if res.StatusCode != http.StatusOK { 32 | t.Error("Status code is not 200") 33 | } 34 | } 35 | 36 | func TestSendMessageCanSendToMultipleRegIDs(t *testing.T) { 37 | srv := httptest.NewServer(http.HandlerFunc(handleRegs)) 38 | FCMServerURL = srv.URL 39 | 40 | defer srv.Close() 41 | 42 | c := NewFCMWithClient("key", &http.Client{}) 43 | 44 | data := map[string]string{ 45 | "msg": "Hello World", 46 | "sum": "Happy Day", 47 | } 48 | 49 | ids := []string{ 50 | "token0", 51 | "token1", 52 | "token2", 53 | } 54 | 55 | res, err := c.Send(Message{ 56 | Data: data, 57 | RegistrationIDs: ids, 58 | }) 59 | if err != nil { 60 | t.Error("Response Error : ", err) 61 | } 62 | if res.StatusCode != http.StatusOK { 63 | t.Error("Status code is not 200") 64 | } 65 | 66 | if res.Success != 2 || res.Fail != 1 { 67 | t.Error("Parsing Success or Fail error") 68 | } 69 | } 70 | 71 | func handleTopic(w http.ResponseWriter, r *http.Request) { 72 | result := `{"message_id":6985435902064854329}` 73 | 74 | fmt.Fprintln(w, result) 75 | } 76 | 77 | func handleRegs(w http.ResponseWriter, r *http.Request) { 78 | result := `{"multicast_id":1003859738309903334,"success":2,"failure":1,"canonical_ids":0,"results":[{"message_id":"0:1448128667408487%ecaaa23db3fd7efd"},{"message_id":"0:1468135657607438%ecafacddf9ff8ead"},{"error":"InvalidRegistration"}]}` 79 | fmt.Fprintln(w, result) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /models.go: -------------------------------------------------------------------------------- 1 | package fcm 2 | 3 | import "time" 4 | 5 | // Message represents fcm request message 6 | type ( 7 | Message struct { 8 | // Data parameter specifies the custom key-value pairs of the message's payload. 9 | // 10 | // For example, with data:{"score":"3x1"}: 11 | // 12 | // On iOS, if the message is sent via APNS, it represents the custom data fields. 13 | // If it is sent via FCM connection server, it would be represented as key value dictionary 14 | // in AppDelegate application:didReceiveRemoteNotification:. 15 | // On Android, this would result in an intent extra named score with the string value 3x1. 16 | // The key should not be a reserved word ("from" or any word starting with "google" or "gcm"). 17 | // Do not use any of the words defined in this table (such as collapse_key). 18 | // Values in string types are recommended. You have to convert values in objects 19 | // or other non-string data types (e.g., integers or booleans) to string. 20 | // 21 | Data interface{} `json:"data,omitempty"` 22 | 23 | // To this parameter specifies the recipient of a message. 24 | // 25 | // The value must be a registration token, notification key, or topic. 26 | // Do not set this field when sending to multiple topics. See Condition. 27 | To string `json:"to,omitempty"` 28 | 29 | // RegistrationIDs for all registration ids 30 | // This parameter specifies a list of devices 31 | // (registration tokens, or IDs) receiving a multicast message. 32 | // It must contain at least 1 and at most 1000 registration tokens. 33 | // Use this parameter only for multicast messaging, not for single recipients. 34 | // Multicast messages (sending to more than 1 registration tokens) 35 | // are allowed using HTTP JSON format only. 36 | RegistrationIDs []string `json:"registration_ids,omitempty"` 37 | 38 | // CollapseKey This parameter identifies a group of messages 39 | // (e.g., with collapse_key: "Updates Available") that can be collapsed, 40 | // so that only the last message gets sent when delivery can be resumed. 41 | // This is intended to avoid sending too many of the same messages when the 42 | // device comes back online or becomes active (see delay_while_idle). 43 | CollapseKey string `json:"collapse_key,omitempty"` 44 | 45 | // Priority Sets the priority of the message. Valid values are "normal" and "high." 46 | // On iOS, these correspond to APNs priorities 5 and 10. 47 | // By default, notification messages are sent with high priority, and data messages 48 | // are sent with normal priority. Normal priority optimizes the client app's battery 49 | // consumption and should be used unless immediate delivery is required. For messages 50 | // with normal priority, the app may receive the message with unspecified delay. 51 | // When a message is sent with high priority, it is sent immediately, and the app 52 | // can wake a sleeping device and open a network connection to your server. 53 | // For more information, see Setting the priority of a message. 54 | Priority string `json:"priority,omitempty"` 55 | 56 | // Notification parameter specifies the predefined, user-visible key-value pairs of 57 | // the notification payload. See Notification payload support for detail. 58 | // For more information about notification message and data message options, see 59 | // Notification 60 | Notification Notification `json:"notification,omitempty"` 61 | 62 | // ContentAvailable On iOS, use this field to represent content-available 63 | // in the APNS payload. When a notification or message is sent and this is set 64 | // to true, an inactive client app is awoken. On Android, data messages wake 65 | // the app by default. On Chrome, currently not supported. 66 | ContentAvailable bool `json:"content_available,omitempty"` 67 | 68 | // DelayWhenIdle When this parameter is set to true, it indicates that 69 | // the message should not be sent until the device becomes active. 70 | // The default value is false. 71 | DelayWhileIdle bool `json:"delay_while_idle,omitempty"` 72 | 73 | // TimeToLive This parameter specifies how long (in seconds) the message 74 | // should be kept in FCM storage if the device is offline. The maximum time 75 | // to live supported is 4 weeks, and the default value is 4 weeks. 76 | // For more information, see 77 | // https://firebase.google.com/docs/cloud-messaging/concept-options#ttl 78 | TimeToLive int `json:"time_to_live,omitempty"` 79 | 80 | // RestrictedPackageName This parameter specifies the package name of the 81 | // application where the registration tokens must match in order to 82 | // receive the message. 83 | RestrictedPackageName string `json:"restricted_package_name,omitempty"` 84 | 85 | // DryRun This parameter, when set to true, allows developers to test 86 | // a request without actually sending a message. 87 | // The default value is false 88 | DryRun bool `json:"dry_run,omitempty"` 89 | 90 | // Condition to set a logical expression of conditions that determine the message target 91 | // This parameter specifies a logical expression of conditions that determine the message target. 92 | // Supported condition: Topic, formatted as "'yourTopic' in topics". This value is case-insensitive. 93 | // Supported operators: &&, ||. Maximum two operators per topic message supported. 94 | Condition string `json:"condition,omitempty"` 95 | 96 | // Currently for iOS 10+ devices only. On iOS, use this field to represent mutable-content in the APNS payload. 97 | // When a notification is sent and this is set to true, the content of the notification can be modified before 98 | // it is displayed, using a Notification Service app extension. This parameter will be ignored for Android and web. 99 | MutableContent bool `json:"mutable_content,omitempty"` 100 | } 101 | 102 | // Downstream result from FCM, sent in the "results" field of the Response packet 103 | Result struct { 104 | // String specifying a unique ID for each successfully processed message. 105 | MessageID string `json:"message_id"` 106 | 107 | // Optional string specifying the canonical registration token for the 108 | // client app that the message was processed and sent to. Sender should 109 | // use this value as the registration token for future requests. 110 | // Otherwise, the messages might be rejected. 111 | RegistrationID string `json:"registration_id"` 112 | 113 | // String specifying the error that occurred when processing the message 114 | // for the recipient. The possible values can be found in table 9 here: 115 | // https://firebase.google.com/docs/cloud-messaging/http-server-ref#table9 116 | Error string `json:"error"` 117 | } 118 | 119 | // Response represents fcm response message - (tokens and topics) 120 | Response struct { 121 | Ok bool 122 | StatusCode int 123 | 124 | // MulticastID a unique ID (number) identifying the multicast message. 125 | MulticastID uint64 `json:"multicast_id"` 126 | 127 | // Success number of messages that were processed without an error. 128 | Success int `json:"success"` 129 | 130 | // Fail number of messages that could not be processed. 131 | Fail int `json:"failure"` 132 | 133 | // CanonicalIDs number of results that contain a canonical registration token. 134 | // A canonical registration ID is the registration token of the last registration 135 | // requested by the client app. This is the ID that the server should use 136 | // when sending messages to the device. 137 | CanonicalIDs int `json:"canonical_ids"` 138 | 139 | // Results Array of objects representing the status of the messages processed. The objects are listed in the same order as the request (i.e., for each registration ID in the request, its result is listed in the same index in the response). 140 | // message_id: String specifying a unique ID for each successfully processed message. 141 | // registration_id: Optional string specifying the canonical registration token for the client app that the message was processed and sent to. Sender should use this value as the registration token for future requests. Otherwise, the messages might be rejected. 142 | // error: String specifying the error that occurred when processing the message for the recipient. The possible values can be found in table 9. 143 | Results []Result `json:"results,omitempty"` 144 | 145 | // The topic message ID when FCM has successfully received the request and will attempt to deliver to all subscribed devices. 146 | MsgID int `json:"message_id,omitempty"` 147 | 148 | // Error that occurred when processing the message. The possible values can be found in table 9. 149 | Err string `json:"error,omitempty"` 150 | 151 | // RetryAfter 152 | RetryAfter string 153 | } 154 | 155 | // Notification notification message payload 156 | Notification struct { 157 | // Title indicates notification title. This field is not visible on iOS phones and tablets. 158 | Title string `json:"title,omitempty"` 159 | 160 | // Body indicates notification body text. 161 | Body string `json:"body,omitempty"` 162 | 163 | // Image should contains the URL of an image that is going to be downloaded on the device and displayed in a notification. 164 | Image string `json:"image,omitempty"` 165 | 166 | // AndroidChannelID The notification's channel id (new in Android O). 167 | // The app must create a channel with this channel ID before any notification with this channel ID is received. 168 | // If you don't send this channel ID in the request, or if the channel ID provided has 169 | // not yet been created by the app, FCM uses the channel ID specified in the app manifest. 170 | AndroidChannelID string `json:"android_channel_id,omitempty"` 171 | 172 | // Sound indicates a sound to play when the device receives a notification. 173 | // Sound files can be in the main bundle of the client app or in the 174 | // Library/Sounds folder of the app's data container. 175 | // See the iOS Developer Library for more information. 176 | // http://apple.co/2jaGqiE 177 | Sound string `json:"sound,omitempty"` 178 | 179 | // Badge indicates the badge on the client app home icon. 180 | Badge string `json:"badge,omitempty"` 181 | 182 | // Icon indicates notification icon. Sets value to myicon for drawable resource myicon. 183 | // If you don't send this key in the request, FCM displays the launcher icon specified 184 | // in your app manifest. 185 | Icon string `json:"icon,omitempty"` 186 | 187 | // Tag indicates whether each notification results in a new entry in the notification 188 | // drawer on Android. If not set, each request creates a new notification. 189 | // If set, and a notification with the same tag is already being shown, 190 | // the new notification replaces the existing one in the notification drawer. 191 | Tag string `json:"tag,omitempty"` 192 | 193 | // Color indicates color of the icon, expressed in #rrggbb format 194 | Color string `json:"color,omitempty"` 195 | 196 | // ClickAction indicates the action associated with a user click on the notification. 197 | // When this is set, an activity with a matching intent filter is launched when user 198 | // clicks the notification. 199 | ClickAction string `json:"click_action,omitempty"` 200 | 201 | // BodyLockKey indicates the key to the body string for localization. Use the key in 202 | // the app's string resources when populating this value. 203 | BodyLocKey string `json:"body_loc_key,omitempty"` 204 | 205 | // BodyLocArgs indicates the string value to replace format specifiers in the body 206 | // string for localization. For more information, see Formatting and Styling. 207 | BodyLocArgs string `json:"body_loc_args,omitempty"` 208 | 209 | // TitleLocKey indicates the key to the title string for localization. 210 | // Use the key in the app's string resources when populating this value. 211 | TitleLocKey string `json:"title_loc_key,omitempty"` 212 | 213 | // TitleLocArgs indicates the string value to replace format specifiers in the title string for 214 | // localization. For more information, see 215 | // https://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling 216 | TitleLocArgs string `json:"title_loc_args,omitempty"` 217 | } 218 | ) 219 | 220 | // GetRetryAfterTime converts the retry after response header to a time.Duration 221 | func (r *Response) GetRetryAfterTime() (time.Duration, error) { 222 | return time.ParseDuration(r.RetryAfter) 223 | } 224 | -------------------------------------------------------------------------------- /models_test.go: -------------------------------------------------------------------------------- 1 | package fcm 2 | 3 | import "testing" 4 | 5 | func TestResponse_GetRetryAfterTime(t *testing.T) { 6 | r := &Response{ 7 | RetryAfter: "120s", 8 | } 9 | _, err := r.GetRetryAfterTime() 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | 14 | } 15 | --------------------------------------------------------------------------------