Thursday, January 29, 2015

(ab)Using APNS for Fun and For Profit

When we started building an event sharing social network, we didn't initially realize we were building a chat/instant messenger application. Once we added the ability to have a discussion around an event, we needed the ability to "live-update" the discussion if someone added a comment while you were in the discussion view (think iMessage). We didn't have a web socket or long-poll infrastructure in place at the time, but needed to get something working quickly.

We discovered we could use push notifications to accomplish this functionality. The challenge was to send a push notification that the app could intercept and react to if the app was in the foreground, but if the app was not in the foreground, iOS needed to ignore the push notification (no banner, sound, or badge update). This was before iOS 7 was released, so there was no official support for "silent" push notifications. We discovered we could send a push notification with an empty alert dictionary. Such a push notification didn't produce a banner if the app was not in the foreground.
{ aps: { alert: {} }, ...} 

This tactic continues to work with iOS 7 and iOS 8, and seems to get around Apple rate-limiting. The notifications are always delivered immediately. I suspect this is due to the alert in the push payload. The server doesn't think it is a silent notification, and therefore doesn't rate-limit the notifications. However, since the alert dictionary is empty, there are no visible or audible artifacts when the iOS device receives the push notifications.

Once iOS 7 was available, we used the new content-available setting to solve another problem. All of our logic to determine an accurate home screen icon badge count was in the client-side code. Because of this, the server could not send a badge value in the push notifications. iOS 7 allowed us to pass
content-available:1
which would wake up our app in the background to retrieve events from the server and calculate the badge count. We have some evidence that this does not work reliably. If the device is not charging, the push notification is received but sometimes the app does not get awakened and iOS logs an error:
“Silent Push: Deny app no resources reason: Insufficient power budget 0”

To sum up, even though it appears we can get around the rate limiting with an empty alert dictionary, iOS does not reliably wake the app to do a background fetch.