APNS (Apple Push Notification Service) with Node JS

galhe2 picture galhe2 · Apr 14, 2016 · Viewed 17k times · Source

I am looking to create APNS (Apple Push Notification Service), where the server will be sending notifications to the iOS devices. I am able to make the push notifications work via PHP using the SAME device token and the SAME certificate, however, I would like to send notifications via Node JS instead of PHP.

I have the following valid files/certificates to help me get started:

  • cert.pem
  • key.pem
  • aps_development.cer
  • cert.p12
  • key.p12,
  • ck.pem

I've been looking through several resources/links such as:

After doing so, I was able to come up with the following sample code, where PASSWORD stands for the password of the key.pem and TOKEN stands for my device's token:

    var apn = require("apn");
    var path = require('path');
    try {
        var options = {
            cert: path.join(__dirname, 'cert.pem'),         // Certificate file path
            key:  path.join(__dirname, 'key.pem'),          // Key file path
            passphrase: '<PASSWORD>',                             // A passphrase for the Key file
            ca: path.join(__dirname, 'aps_development.cer'),// String or Buffer of CA data to use for the TLS connection
            production:false,
            gateway: 'gateway.sandbox.push.apple.com',      // gateway address
            port: 2195,                                     // gateway port
            enhanced: true                                  // enable enhanced format
        };
        var apnConnection = new apn.Connection(options);
        var myDevice = new apn.Device("<TOKEN>");
        var note = new apn.Notification();
        note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
        note.badge = 3;
        note.sound = "ping.aiff";
        note.alert = "You have a new message";
        note.payload = {'msgFrom': 'Alex'};
        note.device = myDevice;
        apnConnection.pushNotification(note);



        process.stdout.write("******* EXECUTED WITHOUT ERRORS************ :");


    } catch (ex) {
        process.stdout.write("ERROR :"+ex);
    }

I get no errors when executing this code, but The problem is that no notification is received on my iOS device. I have also tried setting the ca:null & debug:true (in options var). But same thing happens.

Again, when I use the ck.pem & device token that I have and use it with PHP, it works, but i'm not able to make it work in Node JS. PLEASE HELP!!

Thank you so much!

Answer

Chad Robinson picture Chad Robinson · Apr 15, 2016

You are probably running into the asynchronous nature of NodeJS itself. I use the same node-apn module with great success. But you don't just call it directly like you're used to in PHP - that's a synchronous model that doesn't map from PHP->Node. Your process is exiting before anything can actually happen - the apnConnection.pushNotification(note); is an asynchronous call that just barely gets started before your script returns/exits.

As noted in the node-apn docs you probably want to "listen for" additional events on apnConnection. Here's an excerpt of code that I use to log out various events that are occurring on the connection after it's created:

// We were unable to initialize the APN layer - most likely a cert issue.
connection.on('error', function(error) {
    console.error('APNS: Initialization error', error);
});

// A submission action has completed. This just means the message was submitted, not actually delivered.
connection.on('completed', function(a) {
    console.log('APNS: Completed sending', a);
});

// A message has been transmitted.
connection.on('transmitted', function(notification, device) {
    console.log('APNS: Successfully transmitted message');
});

// There was a problem sending a message.
connection.on('transmissionError', function(errorCode, notification, device) {
    var deviceToken = device.toString('hex').toUpperCase();

    if (errorCode === 8) {
        console.log('APNS: Transmission error -- invalid token', errorCode, deviceToken);
        // Do something with deviceToken here - delete it from the database?
    } else {
        console.error('APNS: Transmission error', errorCode, deviceToken);
    }
});

connection.on('connected', function() {
    console.log('APNS: Connected');
});

connection.on('timeout', function() {
    console.error('APNS: Connection timeout');
});

connection.on('disconnected', function() {
    console.error('APNS: Lost connection');
});

connection.on('socketError', console.log);

Equally important, you need to make sure your script STAYS RUNNING while the async requests are being processed. Most of the time, as you build a bigger and bigger service, you're going to end up with some kind of event loop that does this, and frameworks like ActionHero, ExpressJS, Sails, etc. will do this for you.

In the meantime, you can confirm it with this super-crude loop, which just forces the process to stay running until you hit CTRL+C:

setInterval(function() {
    console.log('Waiting for events...');
}, 5000);