status-go/vendor/github.com/NaySoftware/go-fcm/fcm.go

362 lines
10 KiB
Go

package fcm
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)
const (
// fcm_server_url fcm server url
fcm_server_url = "https://fcm.googleapis.com/fcm/send"
// MAX_TTL the default ttl for a notification
MAX_TTL = 2419200
// Priority_HIGH notification priority
Priority_HIGH = "high"
// Priority_NORMAL notification priority
Priority_NORMAL = "normal"
// retry_after_header header name
retry_after_header = "Retry-After"
// error_key readable error caching !
error_key = "error"
)
var (
// retreyableErrors whether the error is a retryable
retreyableErrors = map[string]bool{
"Unavailable": true,
"InternalServerError": true,
}
// fcmServerUrl for testing purposes
fcmServerUrl = fcm_server_url
)
// FcmClient stores the key and the Message (FcmMsg)
type FcmClient struct {
ApiKey string
Message FcmMsg
}
// FcmMsg represents fcm request message
type FcmMsg struct {
Data interface{} `json:"data,omitempty"`
To string `json:"to,omitempty"`
RegistrationIds []string `json:"registration_ids,omitempty"`
CollapseKey string `json:"collapse_key,omitempty"`
Priority string `json:"priority,omitempty"`
Notification NotificationPayload `json:"notification,omitempty"`
ContentAvailable bool `json:"content_available,omitempty"`
DelayWhileIdle bool `json:"delay_while_idle,omitempty"`
TimeToLive int `json:"time_to_live,omitempty"`
RestrictedPackageName string `json:"restricted_package_name,omitempty"`
DryRun bool `json:"dry_run,omitempty"`
Condition string `json:"condition,omitempty"`
}
// FcmMsg represents fcm response message - (tokens and topics)
type FcmResponseStatus struct {
Ok bool
StatusCode int
MulticastId int64 `json:"multicast_id"`
Success int `json:"success"`
Fail int `json:"failure"`
Canonical_ids int `json:"canonical_ids"`
Results []map[string]string `json:"results,omitempty"`
MsgId int64 `json:"message_id,omitempty"`
Err string `json:"error,omitempty"`
RetryAfter string
}
// NotificationPayload notification message payload
type NotificationPayload struct {
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
Icon string `json:"icon,omitempty"`
Sound string `json:"sound,omitempty"`
Badge string `json:"badge,omitempty"`
Tag string `json:"tag,omitempty"`
Color string `json:"color,omitempty"`
ClickAction string `json:"click_action,omitempty"`
BodyLocKey string `json:"body_loc_key,omitempty"`
BodyLocArgs string `json:"body_loc_args,omitempty"`
TitleLocKey string `json:"title_loc_key,omitempty"`
TitleLocArgs string `json:"title_loc_args,omitempty"`
AndroidChannelID string `json:"android_channel_id,omitempty"`
}
// NewFcmClient init and create fcm client
func NewFcmClient(apiKey string) *FcmClient {
fcmc := new(FcmClient)
fcmc.ApiKey = apiKey
return fcmc
}
// NewFcmTopicMsg sets the targeted token/topic and the data payload
func (this *FcmClient) NewFcmTopicMsg(to string, body map[string]string) *FcmClient {
this.NewFcmMsgTo(to, body)
return this
}
// NewFcmMsgTo sets the targeted token/topic and the data payload
func (this *FcmClient) NewFcmMsgTo(to string, body interface{}) *FcmClient {
this.Message.To = to
this.Message.Data = body
return this
}
// SetMsgData sets data payload
func (this *FcmClient) SetMsgData(body interface{}) *FcmClient {
this.Message.Data = body
return this
}
// NewFcmRegIdsMsg gets a list of devices with data payload
func (this *FcmClient) NewFcmRegIdsMsg(list []string, body interface{}) *FcmClient {
this.newDevicesList(list)
this.Message.Data = body
return this
}
// newDevicesList init the devices list
func (this *FcmClient) newDevicesList(list []string) *FcmClient {
this.Message.RegistrationIds = make([]string, len(list))
copy(this.Message.RegistrationIds, list)
return this
}
// AppendDevices adds more devices/tokens to the Fcm request
func (this *FcmClient) AppendDevices(list []string) *FcmClient {
this.Message.RegistrationIds = append(this.Message.RegistrationIds, list...)
return this
}
// apiKeyHeader generates the value of the Authorization key
func (this *FcmClient) apiKeyHeader() string {
return fmt.Sprintf("key=%v", this.ApiKey)
}
// sendOnce send a single request to fcm
func (this *FcmClient) sendOnce() (*FcmResponseStatus, error) {
fcmRespStatus := new(FcmResponseStatus)
jsonByte, err := this.Message.toJsonByte()
if err != nil {
return fcmRespStatus, err
}
request, err := http.NewRequest("POST", fcmServerUrl, bytes.NewBuffer(jsonByte))
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return fcmRespStatus, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fcmRespStatus, err
}
fcmRespStatus.StatusCode = response.StatusCode
fcmRespStatus.RetryAfter = response.Header.Get(retry_after_header)
if response.StatusCode != 200 {
return fcmRespStatus, nil
}
err = fcmRespStatus.parseStatusBody(body)
if err != nil {
return fcmRespStatus, err
}
fcmRespStatus.Ok = true
return fcmRespStatus, nil
}
// Send to fcm
func (this *FcmClient) Send() (*FcmResponseStatus, error) {
return this.sendOnce()
}
// toJsonByte converts FcmMsg to a json byte
func (this *FcmMsg) toJsonByte() ([]byte, error) {
return json.Marshal(this)
}
// parseStatusBody parse FCM response body
func (this *FcmResponseStatus) parseStatusBody(body []byte) error {
if err := json.Unmarshal([]byte(body), &this); err != nil {
return err
}
return nil
}
// SetPriority Sets the priority of the message.
// Priority_HIGH or Priority_NORMAL
func (this *FcmClient) SetPriority(p string) *FcmClient {
if p == Priority_HIGH {
this.Message.Priority = Priority_HIGH
} else {
this.Message.Priority = Priority_NORMAL
}
return this
}
// SetCollapseKey This parameter identifies a group of messages
// (e.g., with collapse_key: "Updates Available") that can be collapsed,
// so that only the last message gets sent when delivery can be resumed.
// This is intended to avoid sending too many of the same messages when the
// device comes back online or becomes active (see delay_while_idle).
func (this *FcmClient) SetCollapseKey(val string) *FcmClient {
this.Message.CollapseKey = val
return this
}
// SetNotificationPayload sets the notification payload based on the specs
// https://firebase.google.com/docs/cloud-messaging/http-server-ref
func (this *FcmClient) SetNotificationPayload(payload *NotificationPayload) *FcmClient {
this.Message.Notification = *payload
return this
}
// SetContentAvailable On iOS, use this field to represent content-available
// in the APNS payload. When a notification or message is sent and this is set
// to true, an inactive client app is awoken. On Android, data messages wake
// the app by default. On Chrome, currently not supported.
func (this *FcmClient) SetContentAvailable(isContentAvailable bool) *FcmClient {
this.Message.ContentAvailable = isContentAvailable
return this
}
// SetDelayWhileIdle When this parameter is set to true, it indicates that
// the message should not be sent until the device becomes active.
// The default value is false.
func (this *FcmClient) SetDelayWhileIdle(isDelayWhileIdle bool) *FcmClient {
this.Message.DelayWhileIdle = isDelayWhileIdle
return this
}
// SetTimeToLive This parameter specifies how long (in seconds) the message
// should be kept in FCM storage if the device is offline. The maximum time
// to live supported is 4 weeks, and the default value is 4 weeks.
// For more information, see
// https://firebase.google.com/docs/cloud-messaging/concept-options#ttl
func (this *FcmClient) SetTimeToLive(ttl int) *FcmClient {
if ttl > MAX_TTL {
this.Message.TimeToLive = MAX_TTL
} else {
this.Message.TimeToLive = ttl
}
return this
}
// SetRestrictedPackageName This parameter specifies the package name of the
// application where the registration tokens must match in order to
// receive the message.
func (this *FcmClient) SetRestrictedPackageName(pkg string) *FcmClient {
this.Message.RestrictedPackageName = pkg
return this
}
// SetDryRun This parameter, when set to true, allows developers to test
// a request without actually sending a message.
// The default value is false
func (this *FcmClient) SetDryRun(drun bool) *FcmClient {
this.Message.DryRun = drun
return this
}
// PrintResults prints the FcmResponseStatus results for fast using and debugging
func (this *FcmResponseStatus) PrintResults() {
fmt.Println("Status Code :", this.StatusCode)
fmt.Println("Success :", this.Success)
fmt.Println("Fail :", this.Fail)
fmt.Println("Canonical_ids :", this.Canonical_ids)
fmt.Println("Topic MsgId :", this.MsgId)
fmt.Println("Topic Err :", this.Err)
for i, val := range this.Results {
fmt.Printf("Result(%d)> \n", i)
for k, v := range val {
fmt.Println("\t", k, " : ", v)
}
}
}
// IsTimeout check whether the response timeout based on http response status
// code and if any error is retryable
func (this *FcmResponseStatus) IsTimeout() bool {
if this.StatusCode >= 500 {
return true
} else if this.StatusCode == 200 {
for _, val := range this.Results {
for k, v := range val {
if k == error_key && retreyableErrors[v] == true {
return true
}
}
}
}
return false
}
// GetRetryAfterTime converts the retrey after response header
// to a time.Duration
func (this *FcmResponseStatus) GetRetryAfterTime() (t time.Duration, e error) {
t, e = time.ParseDuration(this.RetryAfter)
return
}
// SetCondition to set a logical expression of conditions that determine the message target
func (this *FcmClient) SetCondition(condition string) *FcmClient {
this.Message.Condition = condition
return this
}