go-fcm/instanceid.go

456 lines
12 KiB
Go

package fcm
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
const (
// instance_id_info_with_details_srv_url
instance_id_info_with_details_srv_url = "https://iid.googleapis.com/iid/info/%s?details=true"
// instance_id_info_no_details_srv_url
instance_id_info_no_details_srv_url = "https://iid.googleapis.com/iid/info/%s"
// subscribe_instanceid_to_topic_srv_url
subscribe_instanceid_to_topic_srv_url = "https://iid.googleapis.com/iid/v1/%s/rel/topics/%s"
// batch_add_srv_url
batch_add_srv_url = "https://iid.googleapis.com/iid/v1:batchAdd"
// batch_rem_srv_url
batch_rem_srv_url = "https://iid.googleapis.com/iid/v1:batchRemove"
// apns_batch_import_srv_url
apns_batch_import_srv_url = "https://iid.googleapis.com/iid/v1:batchImport"
// apns_token_key
apns_token_key = "apns_token"
// status_key
status_key = "status"
// reg_token_key
reg_token_key = "registration_token"
// topics
topics = "/topics/"
)
var (
// batchErrors response errors
batchErrors = map[string]bool{
"NOT_FOUND": true,
"INVALID_ARGUMENT": true,
"INTERNAL": true,
"TOO_MANY_TOPICS": true,
}
)
// InstanceIdInfoResponse response for instance id info request
type InstanceIdInfoResponse struct {
Application string `json:"application,omitempty"`
AuthorizedEntity string `json:"authorizedEntity,omitempty"`
ApplicationVersion string `json:"applicationVersion,omitempty"`
AppSigner string `json:"appSigner,omitempty"`
AttestStatus string `json:"attestStatus,omitempty"`
Platform string `json:"platform,omitempty"`
ConnectionType string `json:"connectionType,omitempty"`
ConnectDate string `json:"connectDate,omitempty"`
Error string `json:"error,omitempty"`
Rel map[string]map[string]map[string]string `json:"rel,omitempty"`
}
// SubscribeResponse response for single topic subscribtion
type SubscribeResponse struct {
Error string `json:"error,omitempty"`
Status string
StatusCode int
}
// BatchRequest add/remove request
type BatchRequest struct {
To string `json:"to,omitempty"`
RegTokens []string `json:"registration_tokens,omitempty"`
}
// BatchResponse add/remove response
type BatchResponse struct {
Error string `json:"error,omitempty"`
Results []map[string]string `json:"results,omitempty"`
Status string
StatusCode int
}
// ApnsBatchRequest apns import request
type ApnsBatchRequest struct {
App string `json:"application,omitempty"`
Sandbox bool `json:"sandbox,omitempty"`
ApnsTokens []string `json:"apns_tokens,omitempty"`
}
// ApnsBatchResponse apns import response
type ApnsBatchResponse struct {
Results []map[string]string `json:"results,omitempty"`
Error string `json:"error,omitempty"`
Status string
StatusCode int
}
// GetInfo gets the instance id info
func (this *FcmClient) GetInfo(withDetails bool, instanceIdToken string) (*InstanceIdInfoResponse, error) {
var request_url string = generateGetInfoUrl(instance_id_info_no_details_srv_url, instanceIdToken)
if withDetails == true {
request_url = generateGetInfoUrl(instance_id_info_with_details_srv_url, instanceIdToken)
}
request, err := http.NewRequest("GET", request_url, nil)
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
}
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
infoResponse, err := parseGetInfo(body)
if err != nil {
return nil, err
}
return infoResponse, nil
}
// parseGetInfo parses response to InstanceIdInfoResponse
func parseGetInfo(body []byte) (*InstanceIdInfoResponse, error) {
info := new(InstanceIdInfoResponse)
if err := json.Unmarshal([]byte(body), &info); err != nil {
return nil, err
}
return info, nil
}
// PrintResults prints InstanceIdInfoResponse, for faster debugging
func (this *InstanceIdInfoResponse) PrintResults() {
fmt.Println("Error : ", this.Error)
fmt.Println("App : ", this.Application)
fmt.Println("Auth : ", this.AuthorizedEntity)
fmt.Println("Ver : ", this.ApplicationVersion)
fmt.Println("Sig : ", this.AppSigner)
fmt.Println("Att : ", this.AttestStatus)
fmt.Println("Platform : ", this.Platform)
fmt.Println("Connection: ", this.ConnectionType)
fmt.Println("ConnDate : ", this.ConnectDate)
fmt.Println("Rel : ")
for k, v := range this.Rel {
fmt.Println(k, " --> ")
for k2, v2 := range v {
fmt.Println("\t", k2, "\t|")
fmt.Println("\t\t", "addDate", " : ", v2["addDate"])
}
}
}
// generateGetInfoUrl generate based on with details and the instance token
func generateGetInfoUrl(srv string, instanceIdToken string) string {
return fmt.Sprintf(srv, instanceIdToken)
}
// SubscribeToTopic subscribes a single device/token to a topic
func (this *FcmClient) SubscribeToTopic(instanceIdToken string, topic string) (*SubscribeResponse, error) {
request, err := http.NewRequest("POST", generateSubToTopicUrl(instanceIdToken, topic), nil)
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
}
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
subResponse, err := parseSubscribeResponse(body, response)
if err != nil {
return nil, err
}
return subResponse, nil
}
// parseSubscribeResponse converts a byte response to a SubscribeResponse
func parseSubscribeResponse(body []byte, resp *http.Response) (*SubscribeResponse, error) {
subResp := new(SubscribeResponse)
subResp.Status = resp.Status
subResp.StatusCode = resp.StatusCode
if err := json.Unmarshal(body, &subResp); err != nil {
return nil, err
}
return subResp, nil
}
// PrintResults prints SubscribeResponse, for faster debugging
func (this *SubscribeResponse) PrintResults() {
fmt.Println("Response Status: ", this.Status)
fmt.Println("Response Code : ", this.StatusCode)
if this.StatusCode != 200 {
fmt.Println("Error : ", this.Error)
}
}
// generateSubToTopicUrl generates a url based on the instnace id and topic name
func generateSubToTopicUrl(instaceId string, topic string) string {
Tmptopic := strings.ToLower(topic)
if strings.Contains(Tmptopic, "/topics/") {
tmp := strings.Split(topic, "/")
topic = tmp[len(tmp)-1]
}
return fmt.Sprintf(subscribe_instanceid_to_topic_srv_url, instaceId, topic)
}
// BatchSubscribeToTopic subscribes (many) devices/tokens to a given topic
func (this *FcmClient) BatchSubscribeToTopic(tokens []string, topic string) (*BatchResponse, error) {
jsonByte, err := generateBatchRequest(tokens, topic)
if err != nil {
return nil, err
}
request, err := http.NewRequest("POST", batch_add_srv_url, bytes.NewBuffer(jsonByte))
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
if err != nil {
fmt.Println(err)
}
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
result, err := generateBatchResponse(body)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("Parsing response error")
}
result.Status = response.Status
result.StatusCode = response.StatusCode
return result, nil
}
// BatchUnsubscribeFromTopic unsubscribes (many) devices/tokens from a given topic
func (this *FcmClient) BatchUnsubscribeFromTopic(tokens []string, topic string) (*BatchResponse, error) {
jsonByte, err := generateBatchRequest(tokens, topic)
if err != nil {
fmt.Println(err)
return nil, err
}
request, err := http.NewRequest("POST", batch_rem_srv_url, bytes.NewBuffer(jsonByte))
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
if err != nil {
fmt.Println(err)
}
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
result, err := generateBatchResponse(body)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("Parsing response error")
}
result.Status = response.Status
result.StatusCode = response.StatusCode
return result, nil
}
// PrintResults prints BatchResponse, for faster debugging
func (this *BatchResponse) PrintResults() {
fmt.Println("Error : ", this.Error)
fmt.Println("Status : ", this.Status)
fmt.Println("Status Code : ", this.StatusCode)
for i, val := range this.Results {
if batchErrors[val["error"]] == true {
fmt.Println("ID: ", i, " | ", val["error"])
}
}
}
// generateBatchRequest based on tokens and topic
func generateBatchRequest(tokens []string, topic string) ([]byte, error) {
envelope := new(BatchRequest)
envelope.To = topics + extractTopicName(topic)
envelope.RegTokens = make([]string, len(tokens))
copy(envelope.RegTokens, tokens)
return json.Marshal(envelope)
}
// extractTopicName extract topic name for valid topic name input
func extractTopicName(inTopic string) (result string) {
Tmptopic := strings.ToLower(inTopic)
if strings.Contains(Tmptopic, "/topics/") {
tmp := strings.Split(inTopic, "/")
result = tmp[len(tmp)-1]
return
}
result = inTopic
return
}
// generateBatchResponse converts a byte response to BatchResponse
func generateBatchResponse(resp []byte) (*BatchResponse, error) {
result := new(BatchResponse)
if err := json.Unmarshal(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// ApnsBatchImportRequest apns import requst
func (this *FcmClient) ApnsBatchImportRequest(apnsReq *ApnsBatchRequest) (*ApnsBatchResponse, error) {
jsonByte, err := apnsReq.ToByte()
if err != nil {
return nil, err
}
request, err := http.NewRequest("POST", apns_batch_import_srv_url, bytes.NewBuffer(jsonByte))
request.Header.Set("Authorization", this.apiKeyHeader())
request.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
}
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
result, err := parseApnsBatchResponse(body)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("Parsing Request error")
}
result.Status = response.Status
result.StatusCode = response.StatusCode
return result, nil
}
// ToByte converts ApnsBatchRequest to a byte
func (this *ApnsBatchRequest) ToByte() ([]byte, error) {
data, err := json.Marshal(this)
if err != nil {
return nil, err
}
return data, nil
}
// parseApnsBatchResponse converts apns byte response to ApnsBatchResponse
func parseApnsBatchResponse(resp []byte) (*ApnsBatchResponse, error) {
result := new(ApnsBatchResponse)
if err := json.Unmarshal(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// PrintResults prints ApnsBatchResponse, for faster debugging
func (this *ApnsBatchResponse) PrintResults() {
fmt.Println("Status : ", this.Status)
fmt.Println("StatusCode : ", this.StatusCode)
fmt.Println("Error : ", this.Error)
for i, val := range this.Results {
fmt.Println(i, ":")
fmt.Println("\tAPNS Token", val[apns_token_key])
fmt.Println("\tStatus ", val[status_key])
fmt.Println("\tReg Token ", val[reg_token_key])
}
}