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]) } }