In my previous article An overview of a push notification system, we gained a good understanding of how a push notifications system works. If PNS or provider server says nothing to you, I encourage you to read it first as I am not going to go over that again.
In this article, we will configure a react native application capable of receiving a push notification on a real iPhone device. This notification will be created by our nodeJS server that will then send the notification payload to APNs, the Push Notification Service of Apple.
You will need a mac, an iPhone/iPad, and a valid Apple Developer account to follow this guide 🎯
The first thing to do is to generate a new react-native app from scratch:
++pre>++code>npx react-native init PushNotificationsDemo --template react-native-template-typescript
++/pre>
You could run your newly generated app on an iPhone simulator, but unfortunately, you cannot receive push notifications on a simulator. You will have to create a development provisioning profile to sign and run your app on a real device. If you don't know how to sign your app, you can follow for example this guide.
Once the app is generated and run on a real device, we want to register it to APNs to retrieve the token that will be used to identify the device. We have some configuration to do.
First, go to your apple developer account and look for the App Id used to identify your app:
You have to enable the push notification capability for this App ID:
Open your project in Xcode and enable the Push Notifications capability
Xcode will add the APS environment entitlement to your project:
This entitlement has two values :
It will condition the APNs server on which the device will try to register. There are two APNs environments, a development one and a production one. Use the first one when you are developing a new push notification feature on an iPhone that runs an app signed with a development certificate. Use the second one when your app is deployed in production.
You will not have to manually update the value of the aps-environment key, it is automatically updated by Xcode during the build phase depending on the type of certificate you use to sign the app.
Install react-native-notifications and follow the instructions. Then in your App.tsx file, add the following content at the beginning of the file:
++pre>++code>Notifications.registerRemoteNotifications();
Notifications.events().registerRemoteNotificationsRegistered(
(event: Registered) => {
console.log('Notification token:', event.deviceToken);
},
);
Notifications.events().registerRemoteNotificationsRegistrationFailed(
(event: RegistrationError) => {
console.log('Notification error', event);
},
);
++/pre>
What happens under the hood ? ++code>Notifications.registerRemoteNotifications()++/code> calls the ++code>registerForRemoteNotifications()++/code> method of ++code>UIApplication++/code> to request the device token to APNs. When our app receives the response, it dispatches on of the following event:
We listen to these events with two callbacks responsible to log the result.
If everything went well, you should see a unique token associated with your app and your device in your logs.
That's all for the react-native part, your device is now registered to the APNs, you will use the retrieved token on the server-side to send a notification to your device. Don't forget to write somewhere this token.
We are not going to implement this step in this guide as it doesn't strictly relate to push notifications. The main idea is to trigger a POST request to your provider server once the token is retrieved in ++code>registerRemoteNotificationsRegistered++/code> .
You may want to send:
Once the request is received by your provider server, you will have to persist the body data to the database of your choice. For the sake of simplicity, we are going to hardcode the PNS token on the provider server-side.
Now that our device is registered to APNS and that our provider server has all the required information to target the device, let's create a secured connection to APNS from our provider server and send a notification payload to our device.
We have two ways to establish a trusted connection to APNS:
The token-based connection is recommended by Apple because it's stateless, it's faster (APNs does not have to look for the certificate), the same key can be used for all your environments and the key does not expire. This is the connection we are going to implement in this tutorial.
As a prerequisite, we first need to generate and download the key that will be used to sign our token directly on our Apple developers account.
a. Go to the keys section:
b. Create a new key with the Apple Push Notifications service enabled:
This will generate a public / private key pair. You can download the private key. You will use the private key to sign your authentication token. The public key will be used by Apple to validate your signed token.
Now that we have our private key to sign our token, we can generate it. The token should follow the JWT standard. It is composed of:
++pre>++code>{
"alg": "ES256",
"kid": "YOUR_KEY_IDENTIFIER"
}
++/pre>
++pre>++code>{
"iss": "YOUR_DEVELOPERS_TEAM_ID",
"iat": "CURRENT_TIMESTAMP"
}
++/pre>
Let's implement that in a NodeJS based environment. First, generate a new nodeJS server by following this tutorial. Then you can add the jwt library that will provide you all the necessary tools to generate and sign your token.
++pre>++code>yarn add jsonwebtoken
++/pre>
To generate your token, you can copy/past the following code:
++pre>++code>const authorizationToken = jwt.sign(
{
iss: APPLE_TEAM_ID,
iat: Math.round(new Date().getTime() / 1000),
},
fs.readFileSync(KEY_PATH, "utf8"),
{
header: {
alg: "ES256",
kid: KEY_ID,
},
}
);++/pre>
Don't forget to replace the constants with your values:
The simplest push notification payload you can create can look like this:
++pre>++code>{
"aps": {
"alert": {
"title": "\u2709 You have a new message",
},
},
}
++/pre>
Feel free to customize it as much as you want. You can find the specification here.
To send this payload to APNs, we need to establish an HTTP/2 connection with it. To do so, we can use the http2 module provided by NodeJS. The final simple implementation of our server can look like this:
++pre>++code>const app = express()
const port = 3000
const authorizationToken = jwt.sign(
{
iss: APPLE_TEAM_ID,
iat: Math.round(new Date().getTime() / 1000),
},
fs.readFileSync(KEY_PATH, "utf8"),
{
header: {
alg: "ES256",
kid: KEY_ID,
},
}
);
const http2Client = http2.connect(
IS_PRODUCTION ? 'https://api.push.apple.com' : 'https://api.sandbox.push.apple.com'
);
app.post('/', (req, res) => {
const request = http2Client.request({
':method': 'POST',
':scheme': 'https',
'apns-topic': BUNDLE_ID,
':path': '/3/device/' + DEVICE_TOKEN,
authorization: `bearer ${authorizationToken}`,
});
request.setEncoding('utf8');
request.write(
JSON.stringify({
aps: {
alert: {
title: "\u2709 You have a new message",
},
},
})
);
request.on('end', () => {
res.send('Notification sent !');
});
request.end();
})
app.listen(port, () => {
console.log(`PushNotificationsServerDemo app listening at http://localhost:${port}`)
})
++/pre>
You may have noticed that a lot of code will have to be written to handle the communication with APNs properly. the features we will want to develop can be the following
++code>aps-node++/code> will help you simplify this complexity. To install it you can run:
++pre>++code>yarn add aps
++/pre>
With apn, our server code can be simplified to this:
++pre>++code>const app = express()
const port = 3000
let provider = new apn.Provider({
token: {
key: KEY_PATH,
keyId: KEY_ID,
teamId: APPLE_TEAM_ID
},
production: IS_PRODUCTION
});
app.post('/', (req, res) => {
var note = new apn.Notification();
note.alert = "\u2709 You have a new message";
note.topic = BUNDLE_ID;
provider.send(note, DEVICE_TOKEN).then( (result) => {
res.send('Notification sent !');
});
})
app.listen(port, () => {
console.log(`PushNotificationsServerDemo app listening at http://localhost:${port}`)
})
++/pre>
That all for the backend part! If you run your server (++code>yarn start++/code> command) and generate a post request to http://localhost:3000/ you should receive a push notification on your device!
You've got this far, congrats!!! In this tutorial, we went through the configuration of both a react-native app and a nodeJS server to implement a push notification feature using APNs. As a result, we gain a deeper understanding of the usefulness of the Ids and Keys required by Apple to use their services. However, I do not recommend you implement your backend the way we did. You may encounter performance and scalability issues, you should have a look at some push notification SaaS available out there that will solve these kinds of problems for you. If you have any questions or feedbacks about these two articles on push notifications feel free to contact me on Twitter ?