Apple Push Notification Service (APNs)

Follow this procedure to implement push notification with APNs.

Introduction to APNs push notification

The push notification solution described in this section is based on the Apple Push Notification Service. Familiarize yourself with APNs by visiting the Apple Push Notification Service web site.

Apple Push Notification service allows push servers to send notification message data to registered iOS (and OS X) devices.

The APNs service transports and routes a remote notification from a given provider to a given device. A notification is a short message built from two pieces of data: the device token and the payload.
Note: Each device needs to be identified by its device token, and the provider must send individual notification messages for each registered device.
The system involves the following actors:
  • The Apple Push Notification Service (APNs):

    APNs provides push server and client identification. It also handles all aspects of message queuing and delivery to the target applications running on registered devices. The APNs system includes a feedback service that can be queried to check for devices that have unregistered and no longer need to be notified.

  • The device tokens maintainer:

    A Web Services server program maintaining the database of device tokens, with application user information. This program must listen to new device registration events, store them in a database, and from time to time query the APNs feedback service to check for unregistrations.

  • The push provider:

    This program will send notification messages to the APNs server by using the com.APNS class and TCP request API. The push provider program will query the device token database to know which devices need to be notified.

  • Devices running the Genero app registered to the push notification server:

    Registered devices use the push notification client API to register, get notifications data and unregister from the service.

Note: The database used to store device tokens must be a multi-user database (do not use SQLite for example), since two distinct programs will use the database.

APNs push notification security

iOS apps must be created with an Apple certificate for development or distribution, linked to an App ID (or Bundle ID) with push notification enabled. The provisioning profile used when building the IPA must be linked to the App ID with push enabled. Certificate, provisioning and bundle id must be specified to the GMI buildtool.

To create the push provider linked to your app, usually you need to create two Apple Push Notification certificates linked to your App ID (you select the App ID when you create a push certificate in the Apple member center): One certification for development and another for distribution. For more details about the push provider certificates, see APNs SSL/TLS certificate.

Check also Apple Push Notification documentation for more details about certificate requirements for push notifications.

Identifying target devices

Each APNs client device is identified by a device token. A device token is an opaque identifier of a device that APNs gives to the device when an app registers itself for push notification. It enables APNs to locate in a unique manner the device on which the client app is installed. The device shares the device token with the push provider. The push provider must produce notification messages for each device by including the device token in the message structure.

Important: The mobile app obtains its device token by registering to the APNs service with the registerForRemoteNotifications front call. It is then in charge of sending its device token to the push provider; typically through a RESTFul request. The push provider must collect and store the device tokens, as they need to be specified in a push notification message send by the push provided.

Notification content (payload)

In a notification message, the payload is a JSON-defined property list that specifies how the user of an app on a device is to be alerted.

Important: The size of an APNs notification payload cannot exceed 2 Kilobytes. Make sure that the resulting BYTE variable does not exceed this size limitation. If more information needs to be passed, after receiving the push message, apps must contact the server part to query for more information. However, this is only possible when network is available.

The payload must contain a list of "aps" records. Each "aps" record represents a notification message to be displayed as a hint on the device (for example, by adding a badge number to the app icon). The "aps" records can also contain custom data in a separate set of JSON attributes.

In the Genero mobile app, the notification messages are obtained by using the getRemoteNotifications front call, after a notificationpushed action was detected with an ON ACTION handler.
Important: When an iOS app is in background, silent push notifications can occur, but notification message data (i.e. the payload) may not be available. In such case, GMI is able to detect that a notification arrived (i.e. when the app badge number is greater than zero) and raise the notificationpushed action, but the getRemoteNotifications front call will return no message data (data return param is NULL). If such case, implement a fallback mechanism (based on RESTFul web services for example), to contact the push notification provider and retrieve the message information.
Example of notification record list (JSON array) returned by the getRemoteNotifications front call:
[
 {
  "aps" :
  {
    "alert" : "My first push",
    "badge" : 1,
    "sound" : "default",
    "content-available" : 1
  }
 },
 {
  "aps" :
  {
    "alert" :
    {
      "title" : "Push",
      "body" : "My second push"
    }
    "badge" : 2,
    "sound" : "default",
    "content-available" : 1
  },
  "new_ids" : [ "XV234", "ZF452", "RT563" ],
  "updated_ids" : [ "AC634", "HJ153" ]
 }
]

Badge number handling

With APNs, badge number handling is in charge of the application code: The push provider sends a badge number in the payload records, the app can check the message content, and must communicate with a server component, to indicate that the notification message has been consumed. The server program can then maintain a badge number for each registered device, decrementing the badge number.

In order to set or query the badge number for your app, use the following front calls:

In this tutorial, badge numbers are stored on the server database. The token maintainer handlers requests from apps to sync the badge number for a given device token, and the push provider program reads the database to set the badge number in the notification payload. When the app consumes messages, it queries and resets the app badge number with the getBadgeNumber/setBadgeNumber front calls, and informs the token maintainer to sync the badge number in the central database.

Communication channels

A provider communicates with Apple Push Notification service over a binary network interface, using a streaming TCP socket design in conjunction with binary content:
  • The binary interface of the APNs development environment is available through the URL gateway.sandbox.push.apple.com on port 2195.
  • The binary interface of the APNs production environment is available through the URL gateway.push.apple.com on port 2195.
  • The binary interface of the APNs feedback service is available through the URL feedback.push.apple.com on port 2196.

For each interface, use TLS (or SSL) to establish a secured communication channel. The SSL/TLS certificate required for these connections is obtained from Apple's Member Center.

To establish a TLS session with APNs, an Entrust Secure CA root certificate must be installed on the provider's server. If the server is running OS X, this root certificate is already in the keychain. On other systems the certificate might not be available.

Creating an APNs certificate for the app

The Apple Push Notification Certificate identifies the push notification service for a given mobile app. This certificate will be created from an App ID (a.k.a. Bundle ID) and is used by the APNs system to dispatch the notification message to the registered devices.

For more details, see APNs SSL/TLS certificate.

Implementing the device tokens maintainer

To handle device registrations on the server side of your application, the same code base can be used for APNs and other token-based frameworks.

For more details, see Implementing a token maintainer.

Implementing the push provider

The push provider will produce application notification messages that will be transmitted to the APNs service. The APNs service will then spread them to all registered mobile devices, identified by their device token.

To send notification messages, the push provider must build binary messages by using the com.APNS API, provided by the Web Services library, and send TCP message requests over SSL/TLS to the following URLs:

  • "tcps://gateway.sandbox.apple.com:2195" (for development)
  • "tcps://gateway.push.apple.com:2195" (for production)
Note: In order to establish a secure connection to the APNs framework an SSL/TLS certificate needs to be defined in FGLPROFILE, as described in APNs SSL/TLS certificate.
To send a notification message, the push provider must know the device tokens of the registered devices / applications.
Note: A distinct notification message must be sent for each registered device.

The following example demonstrates how to implement a function to send an APNs notification message. The function takes a device token and a JSON object as parameters. First, build the binary data with the com.APNS.EncodeMessage() method, then POST the data with a com.TCPRequest.doDataRequest() method. In case of success, the TCP request timeout will occur (APNs service only responds immediately in case of error), then use the com.TCPResponse.getDataResponse() method, to get status information. See com.APNS.EncodeMessage() for more details about notification message creation.

IMPORT com
IMPORT security
IMPORT util

FUNCTION apns_send_notif_http(deviceTokenHexa, notif_obj)
    DEFINE deviceTokenHexa STRING,
           notif_obj util.JSONObject
    DEFINE req com.TCPRequest,
           resp com.TCPResponse,
           uuid STRING,
           ecode INTEGER,
           dt DATETIME YEAR TO SECOND,
           exp INTEGER,
           data, err BYTE,
           res STRING

    LOCATE data IN MEMORY
    LOCATE err IN MEMORY

    LET dt = CURRENT + INTERVAL(10) MINUTE TO MINUTE
    LET exp = util.Datetime.toSecondsSinceEpoch(dt)

    TRY
        --LET req = com.TCPRequest.create( "tcps://gateway.push.apple.com:2195" )
        LET req = com.TCPRequest.create( "tcps://gateway.sandbox.push.apple.com:2195" )
        CALL req.setKeepConnection(true)
        CALL req.setTimeout(2) # Wait 2 seconds for APNs to return error code
        LET uuid = security.RandomGenerator.createRandomString(4)
        DISPLAY "PUSH MESSAGE: ", deviceTokenHexa, "/", notif_obj.toString()
        CALL com.APNS.EncodeMessage(
                  data,
                  security.HexBinary.ToBase64(deviceTokenHexa),
                  notif_obj.toString(),
                  uuid,
                  exp,
                  10
             )
        IF LENGTH(data) > 2000 THEN
           LET res = "ERROR : APNS payload cannot exceed 2 kilobytes"
           RETURN res
        END IF
        CALL req.doDataRequest(data)
        TRY
            LET resp = req.getResponse()
            CALL resp.getDataResponse(err)
            CALL com.APNS.DecodeError(err) RETURNING uuid, ecode
            LET res = SFMT("APNS result: UUID: %1, Error code: %2",uuid,ecode)
        CATCH
            CASE STATUS
              WHEN -15553 LET res = "Timeout Push sent without error"
              WHEN -15566 LET res = "Operation failed :", SQLCA.SQLERRM
              WHEN -15564 LET res = "Server has shutdown"
              OTHERWISE   LET res = "ERROR :",STATUS
            END CASE
        END TRY
    CATCH
        LET res = SFMT("ERROR : %1 (%2)", STATUS, SQLCA.SQLERRM)
    END TRY
    RETURN res
END FUNCTION
The next code example implements a function that creates the JSON object defining notification content (payload). That object can be passed to the apns_send_notif_http() function described above:
FUNCTION apns_simple_popup_notif(notif_obj, msg_title, user_data, badge_number)
    DEFINE notif_obj util.JSONObject,
           msg_title, user_data STRING,
           badge_number INTEGER
    DEFINE aps_obj, data_obj util.JSONObject

    LET aps_obj = util.JSONObject.create()
    CALL aps_obj.put("alert", msg_title)
    CALL aps_obj.put("sound", "default")
    CALL aps_obj.put("badge", badge_number)
    CALL aps_obj.put("content-available", 1)
    CALL notif_obj.put("aps", aps_obj)

    LET data_obj = util.JSONObject.create()
    CALL data_obj.put("other_info", user_data)

    CALL notif_obj.put("custom_data", data_obj)

END FUNCTION
The apns_simple_popup_notif() and apns_send_notif_http() functions can then be used as follows:
IMPORT com
IMPORT util

MAIN
    DEFINE reg_ids DYNAMIC ARRAY OF STRING,
           notif_obj util.JSONObject,
           i INTEGER

    LET notif_obj = util.JSONObject.create()
    CALL gcm_simple_popup_notif(notif_obj, "This is my message!", 1)

    LET reg_ids[1] = "APA91bHun..."
    LET reg_ids[2] = "B4AA2q7xa..."
    ...
    FOR i=1 TO reg_ids.getLength()
        DISPLAY gcm_send_notif_http(reg_ids[i], notif_obj)
    END FOR

END MAIN
In order to use the tokens database maintained by a token maintainer program, your APNs push provider can collect device tokens as shown in the example below. Note that the dynamic array contains token ids and badge numbers:
FUNCTION apns_collect_tokens(reg_ids)
    DEFINE reg_ids DYNAMIC ARRAY OF RECORD
                       token STRING,
                       badge INTEGER
                   END RECORD
    DEFINE rec RECORD
               id INTEGER,
               notification_type VARCHAR(10),
               registration_token VARCHAR(250),
               badge_number INTEGER,
               app_user VARCHAR(50),
               reg_date DATETIME YEAR TO FRACTION(3)
           END RECORD,
           x INTEGER
    DECLARE c1 CURSOR FOR
      SELECT * FROM tokens
       WHERE notification_type = "APNS"
    CALL reg_ids.clear()
    FOREACH c1 INTO rec.*
        LET x = reg_ids.getLength() + 1
        LET reg_ids[x].token = rec.registration_token
        LET reg_ids[x].badge = rec.badge_number
    END FOREACH
END FUNCTION
In order to handle badge numbers for each registered device, implement a function to update badge numbers in database:
FUNCTION save_badge_number(token, badge)
    DEFINE token STRING,
           badge INT
    UPDATE tokens SET
        badge_number = badge
    WHERE registration_token = token
END FUNCTION
The above functions can then be used to send a push message to all registered devices:
FUNCTION apns_send_message(msg_title, user_data)
    DEFINE msg_title, user_data STRING
    DEFINE reg_ids DYNAMIC ARRAY OF RECORD
                       token STRING,
                       badge INTEGER
                   END RECORD,
           notif_obj util.JSONObject,
           info_msg STRING,
           new_badge, i INTEGER
    CALL apns_collect_tokens(reg_ids)
    IF reg_ids.getLength() == 0 THEN
       RETURN "No registered devices..."
    END IF
    LET info_msg = "Send:"
    FOR i=1 TO reg_ids.getLength()
        LET new_badge = reg_ids[i].badge + 1
        CALL save_badge_number(reg_ids[i].token, new_badge)
        LET notif_obj = util.JSONObject.create()
        CALL apns_simple_popup_notif(notif_obj, msg_title, user_data, new_badge)
        LET info_msg = info_msg, "\n",
            apns_send_notif_http(reg_ids[i].token, notif_obj)
    END FOR
    RETURN info_msg
END FUNCTION

Handle push notifications in mobile apps

To handle push notifications in mobile apps, the same code base can be used for APNs and other token-based frameworks.

For more details see Handling notifications in the mobile app.