コード例 #1
0
ファイル: push_service.go プロジェクト: uniqush/uniqush-push
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
}
コード例 #2
0
ファイル: payload.go プロジェクト: uniqush/uniqush-push
// validateRawAPNSPayload tests that the client-provided JSON payload can be sent to APNS.
// It converts it to bytes if it is, otherwise it returns a push.PushError.
func validateRawAPNSPayload(payload string) ([]byte, push.PushError) {
	// https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
	var data map[string]interface{}
	err := json.Unmarshal([]byte(payload), &data)
	if data == nil {
		return nil, push.NewBadNotificationWithDetails(fmt.Sprintf("Could not parse payload: %v", err))
	}
	aps, ok := data["aps"]
	if !ok {
		return nil, push.NewBadNotificationWithDetails("Payload missing aps")
	}
	aps_dict, ok := aps.(map[string]interface{})
	if !ok {
		return nil, push.NewBadNotificationWithDetails("aps is not a dictionary")
	}
	if _, ok := aps_dict["alert"]; !ok {
		if content_available, ok := aps_dict["content-available"]; !ok || content_available != "1" {
			return nil, push.NewBadNotificationWithDetails("Missing alert and this is not a silent notification(content-available is not 1)")
		}
	}

	// TODO: Could optionally validate provided fields further according to documentation of the "The Notification Payload" section.
	// Creating a custom struct would make it simpler.
	// (E.g. body, action-loc-key, loc-key, loc-args, badge, sound, content-available, launch-image)
	return []byte(payload), nil
}
コード例 #3
0
ファイル: adm.go プロジェクト: uniqush/uniqush-push
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
}
コード例 #4
0
ファイル: payload.go プロジェクト: uniqush/uniqush-push
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
}
コード例 #5
0
ファイル: adm.go プロジェクト: uniqush/uniqush-push
func notifToMessage(notif *push.Notification) (msg *admMessage, err push.PushError) {
	if notif == nil || len(notif.Data) == 0 {
		err = push.NewBadNotificationWithDetails("empty notification")
		return
	}

	msg = new(admMessage)
	msg.Data = make(map[string]string, len(notif.Data))
	if msggroup, ok := notif.Data["msggroup"]; ok {
		msg.MsgGroup = msggroup
	}
	if rawTTL, ok := notif.Data["ttl"]; ok {
		ttl, err := strconv.ParseInt(rawTTL, 10, 64)
		if err == nil {
			msg.TTL = ttl
		}
	}
	if rawPayload, ok := notif.Data["uniqush.payload.adm"]; ok {
		jsonErr := json.Unmarshal([]byte(rawPayload), &(msg.Data))
		if jsonErr != nil {
			err = push.NewBadNotificationWithDetails(fmt.Sprintf("invalid uniqush.payload.adm: %v", jsonErr))
			return
		}
	} else {
		for k, v := range notif.Data {
			if k == "msggroup" || k == "ttl" {
				continue
			}
			if strings.HasPrefix(k, "uniqush.") { // keys beginning with "uniqush." are reserved by Uniqush.
				continue
			}
			msg.Data[k] = v
		}
	}
	if len(msg.Data) == 0 {
		err = push.NewBadNotificationWithDetails("empty notification")
		return
	}
	return
}
コード例 #6
0
ファイル: push_service.go プロジェクト: uniqush/uniqush-push
// 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)
}