Notes on implementing push notifications via Node.js
2017-03-15

I had to implement push notifications for an application that runs on both iOS and Android. We had Node.js running on the server, and I wanted to integrate the push notification functionality into it.

For some reason, working out all the moving parts and the flow of information involved in generating a push notification is rather tricky.

At the time of writing, the main options are:

The upside of node-apn + fcm-node is that it’s the least complicated solution in terms of dependencies. On the other hand, integrating two separate libraries wasn’t very appealing.

node-pushserver is a full fledged server rather than a library, so it has its own storage (MongoDB) but on the other hand it’s only meant to be hosted internally as it doesn’t have any security mechanisms. I would need to do a significant amount of rework to get it to work on Heroku together with Postgres, so this was a non-starter.

Next I looked at using third party services on top of the platform gateways: Pushwoosh, Urban Airship and Amazon SNS. Pushwoosh and Urban Airship didn’t seem to offer any clear advantage in terms of API or documentation over SNS, while SNS offers extra features because it supports other platforms (eg Windows) and delivery mechanisms (eg SMS) in addition to iOS and Android, which could become useful in the future. I ended up selecting SNS.

The resulting components in the system look like this:

ComponentsWeb clientNodeSNSAPNsGCM/FCMiOS clientAndroid client

In order to receive push notifications, the device has to register with its platform’s notification service:

Once it’s registered, here is how a notification gets sent:

SNS notes

Terminology:

  • ARN - Amazon Resource Name, a scheme for identifying resources and API endpoints.
  • Application - a sort of an entry point into the API; an application has to be created for each platform, with device endpoints grouped under an application
  • Token - push ID retrieved by the device upon registering with the push notification service.
  • Endpoint - address of a given device or topic to send notification to
  • Topic - a channel, a way of sending notifications to multiple devices; individual devices can be subscribed to zero or more topics.

When I was reading documentation on Android push notifications, I found references to FCM and GCM. GCM has been superseded by FCM, although GCM continues to function and this is actually what SNS relies on.

SNS doesn’t actually provide a uniform interface across platforms. Instead, separate payloads have to be specified for each platform unless you happen to be sending a simple text message:

    payload =
        default: message
        APNS: JSON.stringify iosPayload
        GCM: JSON.stringify androidPayload

    sns.publish Message: JSON.stringify(payload), MessageStructure: "json", TargetArn: endpointArn

Note also that platform specific payloads have to be stringified, and then the whole combined payload has to be stringified before it’s sent to SNS.

iOS notes

Production and development on APNs have to be set up separately, leading to confusing results if the wrong push ID is used.

Ad hoc or production builds will be set up for production APNs access, whereas development builds will be set up for development APNs access.

When I was reading about APNs, I found references to a “feedback service”. This used to provide information about devices which have become inactive. However, it turns out that this has been removed, and individual requests to APNs now return a status. Consequently, APNs marks the device endpoint as inactive if it receives a failure response from APNs.

Action buttons have to be defined in the message payload, and there is some corresponding setup to be done on the mobile client side.

It’s apparently possible to use GCM on iOS as well as Android, but it’s not going to be as efficient because it isn’t built into the OS.

Android notes

On Android, notifications can be marked as normal or high priority. The delivery of normal priority notifications can be delayed.