Sending iOS push notifications with NodeJS

Figuring out this whole process took me a little while, so I figured I would share some links that helped me along the way.

Step1: iOS Developer Center

First, head over to the iOS Developer Center and then navigate to the provisioning portal.

Next, create a new AppID, check “Enable for Apple Push Notification service,” click the “configure” button and follow the directions to “Generate a Certificate Signing Request.” Once that is finished, download and install the certificate.

Also, make sure to create a provisioning profile for your app.

Step 2: Export certificates / keys

David Mytton’s article How to renew your Apple Push Notification Push SSL Certificate was really helpful during this step.

Once you have downloaded and installed the certificate, head over to Keychain Acces, right click on the certificate and export it as “apns-prod-cert.p12“. Do the same for the private key associated with the certificate, naming it “apns-prod-key.p12.”

Step 3: Convert .p12 to .pem

Next, run these terminal commands to convert the .p12 files to the .pem format

openssl pkcs12 -clcerts -nokeys -out apns-prod-cert.pem -in apns-prod-cert.p12
openssl pkcs12 -nocerts -out apns-prod-key.pem -in apns-prod-key.p12

and then remove the encryption set by the last command

openssl rsa -in apns-prod-key.pem -out apns-prod-key-noenc.pem

Step 4: Register your app for push notifications

Note: make sure your app is being code signed with the profile you created in step one. If you do not do this, your app cannot register for push notifications.

Start by registering your app for push notifications in your appDelegate.

// Push notifications
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

Also implement the following method.

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken
{
    NSString *tokenStr = [newDeviceToken description];
    NSString *pushToken = [[[tokenStr stringByReplacingOccurrencesOfString:@"" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];

    NSLog(@"token - %@", pushToken);
}

If everything has worked up to this point you should be able to run the app and a UIAlertView will pop up asking if your app is allowed to use push notifications. If you select “Allow” then the above delegate method will be fired. For testing purposes, copy the token and keep it for later.  Normally, you would send this token up to a server and associate it with the user’s account.

 Step 5: Create the server

Apple Push Notifications with Node.js provides a really solid tutorial for creating a NodeJS server instance, reading certificates, and sending notifications.

Here’s my code:

var
    fs = require('fs')
    ,crypto = require('crypto')
    ,tls = require('tls')

    ,certPem = fs.readFileSync('apns-prod-cert.pem', encoding='ascii')
    ,keyPem = fs.readFileSync('apns-prod-key-noenc.pem', encoding='ascii')
    ,caCert = fs.readFileSync('AppleWWDRCA.cer', encoding='ascii')
    ,options = { key: keyPem, cert: certPem, ca: [ caCert ] }
;

connectAPN(function(){});

function connectAPN( next ) {

    var stream = tls.connect(2195, 'gateway.sandbox.push.apple.com', options, function() {

        // connected
        next( !stream.authorized, stream );

    });

    var
        pushnd = { aps: { alert:'This is a test' }, customParam: { foo: 'bar' } } // 'aps' is required
        ,hextoken = 'YOU TOKEN GOES HERE' // Push token from iPhone app. 32 bytes as hexadecimal string
        ,token = hextobin(hextoken)
        ,payload = JSON.stringify(pushnd)
        ,payloadlen = Buffer.byteLength(payload, 'utf-8')
        ,tokenlen = 32
        ,buffer = new Buffer(1 +  4 + 4 + 2 + tokenlen + 2 + payloadlen)
        ,i = 0
        ,msgid = 0xbeefcace // message identifier, can be left 0
        ,seconds = Math.round(new Date().getTime() / 1000) + 1*60*60 // expiry in epoch seconds (1 hour)
        ,payload = JSON.stringify(pushnd);
    ;

    buffer[i++] = 1; // command
    buffer[i++] = msgid >> 24 & 0xFF;
    buffer[i++] = msgid >> 16 & 0xFF;
    buffer[i++] = msgid >> 8 & 0xFF;
    buffer[i++] = msgid & 0xFF;

    // expiry in epoch seconds (1 hour)
    buffer[i++] = seconds >> 24 & 0xFF;
    buffer[i++] = seconds >> 16 & 0xFF;
    buffer[i++] = seconds >> 8 & 0xFF;
    buffer[i++] = seconds & 0xFF;

    buffer[i++] = tokenlen >> 8 & 0xFF; // token length
    buffer[i++] = tokenlen & 0xFF;
    token = hextobin(hextoken);
    token.copy(buffer, i, 0, tokenlen)
    i += tokenlen;
    buffer[i++] = payloadlen >> 8 & 0xFF; // payload length
    buffer[i++] = payloadlen & 0xFF;

    payload = Buffer(payload);
    payload.copy(buffer, i, 0, payloadlen);
    stream.write(buffer);  // write push notification

    stream.on('data', function(data) {

        var
            command = data[0] & 0x0FF  // always 8
            ,status = data[1] & 0x0FF  // error code
            ,msgid = (data[2] << 24) + (data[3] << 16) + (data[4] << 8 ) + (data[5])
        ;

        console.log(command + ':' + status + ':' + msgid);

    });

};

function hextobin(hexstr)
{
    buf = new Buffer(hexstr.length / 2);

    for(var i = 0; i < hexstr.length/2 ; i++)
    {
        buf[i] = (parseInt(hexstr[i * 2], 16) << 4) + (parseInt(hexstr[i * 2 + 1], 16));
    }

    return buf;
 }

Step 6: Listen for notifications

Finally, add one more method to your appDelegate to listen for incoming notifications

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSLog(@"alert msg - %@", [[userInfo objectForKey:@"aps"] objectForKey:@"alert"]);
    NSLog(@"alert custom - %@", [[userInfo objectForKey:@"customParam"] objectForKey:@"foo"]);
}

3 comments

  1. Daniel

    Hi,

    I am using this tutorial and it works. But i have a problem it only works when I run in development, when I upload my app to appstore the push notification never appear. Do you think this happens because i am using the certificate ok Apple development IOS push services(as you explained) instead of using the Apple production IOS push services certificate?

  2. Daniel

    I realize the problem, you should specify this:

    Sandbox: gateway.sandbox.push.apple.com, port 2195. (for the development)
    Production: gateway.push.apple.com, port 2195. (for the release)

Post a comment

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>