Apple Push Notification in Node JS


Certainly! Let's break down the provided Node.js code line by line:
const admin = require('firebase-admin');
const apn = require('apn');
const path = require('path');
require('dotenv').config();
Import necessary modules:
firebase-admin
: Used for interacting with Firebase services.apn
: A Node.js module for working with Apple Push Notification service (APNs).path
: The built-in Node.js module for handling file paths.dotenv
: Used to load environment variables from a .env file intoprocess.env
.
// Path to your service account JSON file
const serviceAccount = require('../../config/firebase_config.json');
- Specify the path to the service account JSON file for Firebase authentication.
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
}
- Check if the Firebase admin app is not already initialized, and if not, initialize it with the provided service account credentials.
// APNs initialization
const apnKeyPath = path.resolve(__dirname, 'AuthKey_5B2FFG64P6.p8');
const apnProvider = new apn.Provider({
token: {
key: apnKeyPath,
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID,
},
production: true // Set to true for production
});
Set up APNs (Apple Push Notification service) initialization:
apnKeyPath
: Specify the path to the authentication key file for APNs.apnProvider
: Create a new instance of the APNs Provider using the specified key, key ID, team ID, and set the environment to production.
// Function to send push notifications
const sendPushNotification = async (req, res) => {
try {
const { apnDeviceToken } = req.body;
if (!apnDeviceToken) {
return res.status(400).json({ error: 'APNs device token is missing in the request body' });
}
Define an asynchronous function
sendPushNotification
that takes a request and response as parameters.Extract the
apnDeviceToken
from the request body and check if it's missing, returning a 400 error if so.
// Send message via APNs directly
const apnNote = new apn.Notification({
alert: 'Hello, World!',
sound: 'default',
topic: 'com.example.prayojanaNew' // Replace with your app's bundle identifier
});
- Create an APNs Notification object (
apnNote
) with a simple message and sound. Thetopic
should be replaced with your app's bundle identifier.
const apnResponse = await apnProvider.send(apnNote, apnDeviceToken);
console.log('APNs Response:', apnResponse, 'for device:', apnDeviceToken);
- Send the APNs notification using the
send
method of the APNs provider and log the response.
// Log the failed devices
if (apnResponse.failed && apnResponse.failed.length > 0) {
const failedDevice = apnResponse.failed[0];
console.error('Failed devices:', apnResponse.failed);
if (failedDevice.response && failedDevice.response.reason === 'BadDeviceToken') {
console.log('Device token is invalid. Update your records accordingly.');
}
}
- If there are failed devices in the response, log the information. If the failure reason is 'BadDeviceToken', log a message indicating that the device token is invalid.
return res.json({ message: 'Notification sent successfully' });
} catch (error) {
console.error('Error sending notification:', error);
return res.status(500).json({ error: 'Failed to send notification' });
}
};
- If the notification is sent successfully, respond with a success message. If there's an error during the process, log the error and respond with a 500 status and an error message.
// Export the function
module.exports = { sendPushNotification };
- Export the
sendPushNotification
function to make it available for use in other parts of your application.
This code essentially sets up a Node.js server function to send push notifications using Firebase for authentication and the APNs service for iOS devices.
COMPLETE CODE
Certainly! Below is the code for sending APNs (Apple Push Notification Service) notifications in Node.js using the apn
library. I've also added comments to explain each part of the code:
// Import necessary libraries and modules
const admin = require('firebase-admin');
const apn = require('apn');
const path = require('path');
require('dotenv').config(); // Load environment variables from .env file
// Path to your Firebase service account JSON file
const serviceAccount = require('../../config/firebase_config.json');
// Initialize Firebase Admin SDK
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
}
// Path to your APNs authentication key file
const apnKeyPath = path.resolve(__dirname, 'AuthKey_5B2FFG64P6.p8');
// Initialize APNs provider
const apnProvider = new apn.Provider({
token: {
key: apnKeyPath,
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID,
},
production: true, // Set to true for production
});
// Function to send push notifications via APNs
const sendPushNotification = async (req, res) => {
try {
// Extract APNs device token from the request body
const { apnDeviceToken } = req.body;
// Check if the device token is provided
if (!apnDeviceToken) {
return res.status(400).json({ error: 'APNs device token is missing in the request body' });
}
// Create an APNs notification with a default sound and alert message
const apnNote = new apn.Notification({
alert: 'Hello, World!',
sound: 'default', // You can replace 'default' with the name of a specific sound file in your app
topic: 'com.example.prayojanaNew', // Replace with your app's bundle identifier
});
// Send the APNs notification to the specified device token
const apnResponse = await apnProvider.send(apnNote, apnDeviceToken);
// Log the APNs response and the device token
console.log('APNs Response:', apnResponse, 'for device:', apnDeviceToken);
// Log the failed devices, if any
if (apnResponse.failed && apnResponse.failed.length > 0) {
const failedDevice = apnResponse.failed[0];
console.error('Failed devices:', apnResponse.failed);
// Check if the failure reason is a bad device token
if (failedDevice.response && failedDevice.response.reason === 'BadDeviceToken') {
console.log('Device token is invalid. Update your records accordingly.');
}
}
// Return a success message in the response
return res.json({ message: 'Notification sent successfully' });
} catch (error) {
// Handle errors and return an error response
console.error('Error sending notification:', error);
return res.status(500).json({ error: 'Failed to send notification' });
}
};
// Export the function for use in other modules
module.exports = { sendPushNotification };
This code sets up a Node.js server route to handle incoming requests and send push notifications using the APNs service. It uses the apn
library for APNs communication and assumes you have set up the necessary environment variables for the APNs key information (APN_KEY_ID
and APN_TEAM_ID
). Make sure to replace the placeholder values with your actual bundle identifier and adjust the code as needed for your specific use case.
Here is the complete code Certainly! Let's go into more detail, providing line-by-line explanations for the code:
const admin = require('firebase-admin');
const apn = require('apn');
const pool = require('../../config/db');
const serviceAccount = require('../../config/firebase_config.json');
const path = require('path');
require('dotenv').config();
- Imports: Import necessary libraries and modules.
admin
is the Firebase Admin SDK for interacting with Firebase services,apn
is the Apple Push Notification Service library,pool
is likely a PostgreSQL connection pool, andserviceAccount
is the configuration file for Firebase.path
is a Node.js module for handling file paths, anddotenv
is used for loading environment variables from a.env
file.
const apnProvider = new apn.Provider({
token: {
key: 'config/AuthKey_5B2FFG64P6.p8',
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID,
},
production: true, // Set to true for production
});
- APN Initialization: Create a new instance of the APN Provider using the provided authentication information. The APN key file, key ID, and team ID are loaded from environment variables. The
production
flag is set based on the environment.
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
}
- Firebase Admin Initialization: Initialize the Firebase Admin SDK if it hasn't been initialized already. It uses the service account credentials from the provided file.
const sendPushNotification = async (req, res) => {
try {
// Functionality to send push notifications...
} catch (error) {
console.error('Error occurred:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
module.exports = sendPushNotification;
- Function Declaration: Define the
sendPushNotification
function as an asynchronous function. This function will handle the logic for sending push notifications.
Now, let's go into the sendPushNotification
function:
const { user_id, event_type, ref_id } = req.body;
const { processedData } = req;
- Request Parsing: Extract relevant data from the request body, assuming it's a JSON payload containing
user_id
,event_type
, andref_id
. Additionally, it expectsprocessedData
in the request, likely processed by middleware.
// Get APN registration tokens
const apnTokensResult = await pool.query('SELECT reg_id FROM notification_devices WHERE user_id = $1 AND is_not_expired = true', [user_id]);
const apnTokens = apnTokensResult.rows.map((row) => row.reg_id);
- Database Query (APN): Retrieve Apple Push Notification (APN) registration tokens from the database for the specified user_id, where tokens are not expired.
// Get FCM registration tokens
const fcmTokensResult = await pool.query('SELECT reg_id FROM notification_devices WHERE user_id = $1 AND is_not_expired = true AND device = $2', [user_id, 'Android']);
const fcmTokens = fcmTokensResult.rows.map((row) => row.reg_id);
- Database Query (FCM): Retrieve Firebase Cloud Messaging (FCM) registration tokens for Android devices from the database for the specified user_id, where tokens are not expired.
if (apnTokens.length === 0 && fcmTokens.length === 0) {
return res.status(404).json({ message: 'Registration IDs not found' });
}
- Token Validation: If no APN or FCM tokens are found, return a 404 status with a corresponding message.
const messages = [];
const invalidTokens = [];
- Arrays Initialization: Initialize arrays to store push notification messages and invalid tokens.
const generateNotificationMessage = (event, processedData) => {
let title = '';
let message = '';
switch (event) {
// Cases for different events...
}
return { title, message };
};
- Message Generation Function: Define a function to generate notification messages based on the event type and processed data.
// Generate APN notifications
apnTokens.forEach((token) => {
const { title, message } = generateNotificationMessage(event_type, processedData);
const apnNote = new apn.Notification({
alert: {
title: title,
body: message,
},
sound: 'default',
topic: 'com.example.prayojanaNew', // Replace with your app's bundle identifier
payload: {
ref_id: '' + ref_id,
type: event_type,
},
});
messages.push({ apnNote, token, platform: 'apn' });
});
- APN Message Generation: Iterate over APN tokens, generate APN notification objects, and push them to the messages array.
// Generate FCM notifications
fcmTokens.forEach((token) => {
const { title, message } = generateNotificationMessage(event_type, processedData);
const fcmMessage = {
notification: {
title: title,
body: message,
},
data: {
ref_id: '' + ref_id,
type: event_type,
},
token: token,
};
messages.push({ fcmMessage, token, platform: 'fcm' });
});
- FCM Message Generation: Iterate over FCM tokens, generate FCM notification objects, and push them to the messages array.
const responses = await Promise.allSettled(
messages.map(async (message) => {
if (message.platform === 'apn') {
return apnProvider.send(message.apnNote, message.token);
} else {
return admin.messaging().send(message.fcmMessage);
}
})
);
- Concurrent Message Sending: Use
Promise.allSettled
to send APN and FCM messages concurrently. Handle responses based on the platform.
responses.forEach((response, index) => {
if (response.status === 'rejected') {
invalidTokens.push(messages[index].token);
}
});
- Handle Responses: Iterate over responses, collect invalid tokens for rejected notifications.
if (invalidTokens.length > 0) {
await pool.query('UPDATE notification_devices SET is_not_expired = false WHERE reg_id = ANY($1)', [invalidTokens]);
}
- Update Database: If there are invalid tokens, update the database to mark them as expired.
res.status(200).json({ message: 'Notifications sent successfully', reg_id: invalidTokens });
- Response: Send a success response with a message and information about invalid tokens.
This documentation provides a detailed explanation of each part of the code. To execute this code:
Set up a PostgreSQL database and configure the connection in
config/db.js
.Create the necessary tables and data in the database.
Configure the paths and environment variables in the code.
Install required packages:
npm install apn firebase-admin pg dotenv
.Run the application using Node.js.
Make sure to handle secure storage of sensitive information like API keys and credentials. Additionally, ensure that the necessary certificates and configurations are set up for APN and FCM.
To combine the FCM and APN functionality into a single route, you can create a unified route handler that calls both notify
and sendPushNotification
. Here's an example of how you can achieve this:
const admin = require('firebase-admin');
const apn = require('apn');
const pool = require('../../config/db');
const serviceAccount = require('../../config/firebase_config.json');
const path = require('path');
require('dotenv').config();
// APNs initialization
const apnProvider = new apn.Provider({
token: {
key: 'config/AuthKey_5B2FFG64P6.p8',
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID,
},
production: true, // Set to true for production
});
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
}
const sendCombinedNotifications = async (req, res) => {
try {
const { user_id, event_type, ref_id } = req.body;
const { processedData } = req;
// Get APN registration tokens
const apnTokensResult = await pool.query('SELECT reg_id FROM notification_devices WHERE user_id = $1 AND is_not_expired = true', [user_id]);
const apnTokens = apnTokensResult.rows.map((row) => row.reg_id);
// Get FCM registration tokens
const fcmTokensResult = await pool.query('SELECT reg_id FROM notification_devices WHERE user_id = $1 AND is_not_expired = true AND device = $2', [user_id, 'Android']);
const fcmTokens = fcmTokensResult.rows.map((row) => row.reg_id);
if (apnTokens.length === 0 && fcmTokens.length === 0) {
return res.status(404).json({ message: 'Registration IDs not found' });
}
const messages = [];
const invalidTokens = [];
const generateNotificationMessage = (event, processedData) => {
let title = '';
let message = '';
switch (event) {
// Cases for different events
case 'assign_member':
title = 'NEW MEMBER ';
message = `You have been assigned a new member ${processedData.memberName} by ${processedData.userName}.`;
break;
case 'update_member_details':
title = 'MEMBER DETAILS ';
message = `Your member ${processedData.memberName} details have been updated by the ${processedData.userName}.`;
break;
case 'switch_member':
title = 'MEMBER SWITCHED';
message = `Your member has been switched to a new care buddy ${processedData.carebuddy_id}.`;
break;
// notification for tasks
case 'created_task':
title = 'NEW TASK ';
message = `You have a new task ${processedData.taskName} assigned by ${processedData.userName}.`;
break;
case 'updated_task':
title = 'TASK UPDATED';
message = `Your task ${processedData.taskName} is updated by ${processedData.userName}.`;
break;
// notification for interactions
case 'created_interaction':
title = 'NEW INTERACTION';
message = `You have a new interaction ${processedData.interactionTitle} assigned by ${processedData.userName}.`;
break;
case 'updated_interaction':
title = 'INTERACTION UPDATED';
message = `Your interaction ${processedData.interactionTitle} is updated by ${processedData.userName}.`;
break;
default:
title = 'DEFAULT TITLE';
message = 'Default notification message.';
}
return { title, message };
};
// Generate APN notifications
apnTokens.forEach((token) => {
const { title, message } = generateNotificationMessage(event_type, processedData);
const apnNote = new apn.Notification({
alert: {
title: title,
body: message,
sound: 'default',
},
topic: 'com.example.prayojanaNew', // Replace with your app's bundle identifier
payload: {
ref_id: '' + ref_id,
type: event_type,
},
});
messages.push({ apnNote, token, platform: 'apn' });
});
// Generate FCM notifications
fcmTokens.forEach((token) => {
const { title, message } = generateNotificationMessage(event_type, processedData);
const fcmMessage = {
notification: {
title: title,
body: message,
},
data: {
ref_id: '' + ref_id,
type: event_type,
},
token: token,
};
messages.push({ fcmMessage, token, platform: 'fcm' });
});
// Send APN and FCM notifications concurrently
const responses = await Promise.allSettled(
messages.map(async (message) => {
if (message.platform === 'apn') {
return apnProvider.send(message.apnNote, message.token);
} else {
return admin.messaging().send(message.fcmMessage);
}
})
);
// Handle responses
responses.forEach((response, index) => {
if (response.status === 'rejected') {
invalidTokens.push(messages[index].token);
}
});
// Update expired tokens in the database
if (invalidTokens.length > 0) {
await pool.query('UPDATE notification_devices SET is_not_expired = false WHERE reg_id = ANY($1)', [invalidTokens]);
}
res.status(200).json({ message: 'Notifications sent successfully', reg_id: invalidTokens });
} catch (error) {
console.error('Error occurred:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
module.exports = sendCombinedNotifications;
This code combines both APN and FCM functionality into a single route handler called sendCombinedNotifications
. It retrieves registration tokens for both APN and FCM separately and then generates and sends notifications concurrently. The responses are handled collectively, and expired tokens are updated in the database. The final response includes a message and the list of invalid tokens.
Subscribe to my newsletter
Read articles from M.RAGHURAM directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
