func admSinglePush(psp *push.PushServiceProvider, dp *push.DeliveryPoint, data []byte, notif *push.Notification) (string, push.PushError) { client := &http.Client{} req, err := admNewRequest(psp, dp, data) if err != nil { return "", err } defer req.Body.Close() resp, httpErr := client.Do(req) if httpErr != nil { return "", push.NewErrorf("Failed to send adm push: %v", httpErr.Error()) } defer resp.Body.Close() id := resp.Header.Get("x-amzn-RequestId") if resp.StatusCode != 200 { if resp.StatusCode == 503 || resp.StatusCode == 500 || resp.StatusCode == 429 { // By default, we retry after one minute. retryAfter := resp.Header.Get("Retry-After") retrySecond := 60 if retryAfter != "" { var retryErr error retrySecond, retryErr = strconv.Atoi(retryAfter) if retryErr != nil { retrySecond = 60 } } retryDuration := time.Duration(retrySecond) * time.Second err = push.NewRetryError(psp, dp, notif, retryDuration) return id, err } body, ioErr := ioutil.ReadAll(resp.Body) if ioErr != nil { return "", push.NewErrorf("Failed to read adm response: %v", err) } var fail admPushFailResponse jsonErr := json.Unmarshal(body, &fail) if jsonErr != nil { return "", push.NewErrorf("%v: %v", resp.StatusCode, string(body)) } reason := strings.ToLower(fail.Reason) switch reason { case "messagetoolarge": err = push.NewBadNotificationWithDetails("MessageTooLarge") case "invalidregistrationid": err = push.NewBadDeliveryPointWithDetails(dp, "InvalidRegistrationId") case "accesstokenexpired": // retry would fix it. err = push.NewRetryError(psp, dp, notif, 10*time.Second) default: err = push.NewErrorf("%v: %v", resp.StatusCode, fail.Reason) } return "", err } return id, nil }
func apnsresToError(apnsres *common.APNSResult, psp *push.PushServiceProvider, dp *push.DeliveryPoint) push.PushError { var err push.PushError switch apnsres.Status { case 0: err = nil case 1: err = push.NewBadDeliveryPointWithDetails(dp, "Processing Error") case 2: err = push.NewBadDeliveryPointWithDetails(dp, "Missing Device Token") case 3: err = push.NewBadNotificationWithDetails("Missing topic") case 4: err = push.NewBadNotificationWithDetails("Missing payload") case 5: err = push.NewBadNotificationWithDetails("Invalid token size") case 6: err = push.NewBadNotificationWithDetails("Invalid topic size") case 7: err = push.NewBadNotificationWithDetails("Invalid payload size") case 8: // err = NewBadDeliveryPointWithDetails(req.dp, "Invalid Token") // This token is invalid, we should unsubscribe this device. err = push.NewUnsubscribeUpdate(psp, dp) default: err = push.NewErrorf("Unknown Error: %d", apnsres.Status) } return err }
func toAPNSPayload(n *push.Notification) ([]byte, push.PushError) { // If "uniqush.payload.apns" is provided, then that will be used instead of the other POST parameters. if payloadJSON, ok := n.Data["uniqush.payload.apns"]; ok { bytes, err := validateRawAPNSPayload(payloadJSON) return bytes, err } payload := make(map[string]interface{}) aps := make(map[string]interface{}) alert := make(map[string]interface{}) for k, v := range n.Data { switch k { case "msg": alert["body"] = v case "action-loc-key": alert[k] = v case "loc-key": alert[k] = v case "loc-args": alert[k] = parseList(v) case "badge", "content-available": b, err := strconv.Atoi(v) if err != nil { continue } else { aps[k] = b } case "sound": aps["sound"] = v case "img": alert["launch-image"] = v case "id": continue case "expiry": continue case "ttl": continue default: if strings.HasPrefix(k, "uniqush.") { // keys beginning with "uniqush." are reserved by uniqush. continue } payload[k] = v } } aps["alert"] = alert payload["aps"] = aps j, err := common.MarshalJSONUnescaped(payload) if err != nil { return nil, push.NewErrorf("Failed to convert notification data to JSON: %v", err) } if len(j) > maxPayLoadSize { return nil, push.NewBadNotificationWithDetails("payload is too large") } return j, nil }
func (self *admPushService) notifToJSON(notif *push.Notification) ([]byte, push.PushError) { msg, err := notifToMessage(notif) if err != nil { return nil, err } data, jsonErr := json.Marshal(msg) if jsonErr != nil { return nil, push.NewErrorf("Failed to marshal message: %v", jsonErr) } return data, nil }
// Push will read all of the delivery points to send to from dpQueue and send responses on resQueue before closing the channel. If the notification data is invalid, // it will send only one response. func (self *pushService) Push(psp *push.PushServiceProvider, dpQueue <-chan *push.DeliveryPoint, resQueue chan<- *push.PushResult, notif *push.Notification) { defer close(resQueue) // Profiling // self.updateCheckPoint("") var err push.PushError req := new(common.PushRequest) req.PSP = psp req.Payload, err = toAPNSPayload(notif) if err == nil && len(req.Payload) > self.requestProcessor.GetMaxPayloadSize() { err = push.NewBadNotificationWithDetails(fmt.Sprintf("payload is too large: %d > %d", len(req.Payload), self.requestProcessor.GetMaxPayloadSize())) } if err != nil { res := new(push.PushResult) res.Provider = psp res.Content = notif res.Err = push.NewErrorf("Failed to create push: %v", err) resQueue <- res for _ = range dpQueue { } return } unixNow := uint32(time.Now().Unix()) expiry := unixNow + 60*60 if ttlstr, ok := notif.Data["ttl"]; ok { ttl, err := strconv.ParseUint(ttlstr, 10, 32) if err == nil { expiry = unixNow + uint32(ttl) } } req.Expiry = expiry req.Devtokens = make([][]byte, 0, 10) dpList := make([]*push.DeliveryPoint, 0, 10) for dp := range dpQueue { res := new(push.PushResult) res.Destination = dp res.Provider = psp res.Content = notif devtoken, ok := dp.FixedData["devtoken"] if !ok { res.Err = push.NewBadDeliveryPointWithDetails(dp, "NoDevtoken") resQueue <- res continue } btoken, err := hex.DecodeString(devtoken) if err != nil { res.Err = push.NewBadDeliveryPointWithDetails(dp, err.Error()) resQueue <- res continue } req.Devtokens = append(req.Devtokens, btoken) dpList = append(dpList, dp) } n := len(req.Devtokens) lastId := self.getMessageIds(n) req.MaxMsgId = lastId req.DPList = dpList // We send this request object to be processed by pushMux goroutine, to send responses/errors back. errChan := make(chan push.PushError) resChan := make(chan *common.APNSResult, n) req.ErrChan = errChan req.ResChan = resChan self.requestProcessor.AddRequest(req) // errChan closed means the message(s) is/are sent successfully to the APNs. // However, we may have not yet receieved responses from APNS - those are sent on resChan for err = range errChan { res := new(push.PushResult) res.Provider = psp res.Content = notif if _, ok := err.(*push.ErrorReport); ok { res.Err = push.NewErrorf("Failed to send payload to APNS: %v", err) } else { res.Err = err } resQueue <- res } // Profiling // self.updateCheckPoint("sending the message takes") if err != nil { return } for i, dp := range dpList { if dp != nil { r := new(push.PushResult) r.Provider = psp r.Content = notif r.Destination = dp mid := req.GetId(i) r.MsgId = fmt.Sprintf("apns:%v-%v", psp.Name(), mid) r.Err = nil resQueue <- r } } // Wait for the unserialized responses from APNS asynchronously - these will not affect what we send our clients for this request, but will affect subsequent requests. go self.waitResults(psp, dpList, lastId, resChan) }
func requestToken(psp *push.PushServiceProvider) push.PushError { var ok bool var clientid string var cserect string if _, ok = psp.VolatileData["token"]; ok { if exp, ok := psp.VolatileData["expire"]; ok { unixsec, err := strconv.ParseInt(exp, 10, 64) if err == nil { deadline := time.Unix(unixsec, int64(0)) if deadline.After(time.Now()) { fmt.Printf("We don't need to request another token\n") return nil } } } } if clientid, ok = psp.FixedData["clientid"]; !ok { return push.NewBadPushServiceProviderWithDetails(psp, "NoClientID") } if cserect, ok = psp.FixedData["clientsecret"]; !ok { return push.NewBadPushServiceProviderWithDetails(psp, "NoClientSecret") } form := url.Values{} form.Set("grant_type", "client_credentials") form.Set("scope", "messaging:push") form.Set("client_id", clientid) form.Set("client_secret", cserect) req, err := http.NewRequest("POST", admTokenURL, bytes.NewBufferString(form.Encode())) if err != nil { return push.NewErrorf("NewRequest error: %v", err) } defer req.Body.Close() req.Header.Add("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { return push.NewErrorf("Do error: %v", err) } defer resp.Body.Close() content, err := ioutil.ReadAll(resp.Body) if err != nil { return push.NewBadPushServiceProviderWithDetails(psp, err.Error()) } if resp.StatusCode != 200 { var fail tokenFailObj err = json.Unmarshal(content, &fail) if err != nil { return push.NewBadPushServiceProviderWithDetails(psp, err.Error()) } reason := strings.ToUpper(fail.Reason) switch reason { case "INVALID_SCOPE": reason = "ADM is not enabled. Enable it on the Amazon Mobile App Distribution Portal" } return push.NewBadPushServiceProviderWithDetails(psp, fmt.Sprintf("%v:%v (%v)", resp.StatusCode, reason, fail.Description)) } var succ tokenSuccObj err = json.Unmarshal(content, &succ) if err != nil { return push.NewBadPushServiceProviderWithDetails(psp, err.Error()) } expire := time.Now().Add(time.Duration(succ.Expire-60) * time.Second) psp.VolatileData["expire"] = fmt.Sprintf("%v", expire.Unix()) psp.VolatileData["token"] = succ.Token psp.VolatileData["type"] = succ.Type return push.NewPushServiceProviderUpdate(psp) }