iOS Communication Notifications for React Native with OneSignal

Christopher Mathews
5 min readDec 30, 2023

--

Introduction

Are you working on creating or improving chat functionality in your React Native App? If so, this tutorial is for you. Information on implementing Communication Notifications in general is sparse, especially for React Native. I am going to give you an example using the react-native-onesignal package and the OneSignal REST API. It is worth noting that this should be fairly easy to replicate using other notification providers, so long as they allow you to include custom data in the payload and you are able to edit NotificationService.swift in the native iOS project. I want to give a huge thanks to @gpminsuk for providing part of this code, which is what helped me get started.

Setting Up The React Native OneSignal SDK

Follow the official setup guide. Configuration can change and that will stay up to date.

Changes to NotificationService.swift

This is where the magic happens. If the app is running on iOS 15 or higher, this updated code will create a Communication Notification. Otherwise, a regular notification will show since older iOS versions do not support Communication Notifications. Copy the new code below, and replace the contents of NotificationService.swift with it.

import UserNotifications
import Intents
import OneSignalExtension

class NotificationService: UNNotificationServiceExtension {

private var contentHandler: ((UNNotificationContent) -> Void)?
private var receivedRequest: UNNotificationRequest?
private var bestAttemptContent: UNMutableNotificationContent?

private func genMessageIntent(from request: UNNotificationRequest) -> INSendMessageIntent? {
guard let custom = request.content.userInfo["custom"] as? [String: Any],
let a = custom["a"] as? [String: Any],
let name = a["name"] as? String, // Name that will appear
let urlString = a["url"] as? String, // Photo that will appear
let url = URL(string: urlString) else {
return nil
}

let handle = INPersonHandle(value: nil, type: .unknown)
let avatar = INImage(url: url)
let sender = INPerson(personHandle: handle, nameComponents: nil, displayName: name, image: avatar, contactIdentifier: nil, customIdentifier: nil)

if #available(iOSApplicationExtension 14.0, *) {
return INSendMessageIntent(
recipients: nil,
outgoingMessageType: .outgoingMessageText,
content: nil,
speakableGroupName: nil,
conversationIdentifier: nil,
serviceName: nil,
sender: sender,
attachments: nil
)
} else {
// Fallback on earlier versions
return nil
}
}

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.receivedRequest = request
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

let userInfo = request.content.userInfo
print("Payload:", userInfo)


if #available(iOSApplicationExtension 15.0, *) {
guard let intent = genMessageIntent(from: request) else {
forwardRequestToExtension()
return
}

let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming

interaction.donate { [weak self] error in
guard let self = self, error == nil else { return }

do {
let content = try request.content.updating(from: intent)
self.bestAttemptContent = (content.mutableCopy() as? UNMutableNotificationContent)
self.forwardRequestToExtension()
} catch {
// Handle errors appropriately
}
}
} else {
forwardRequestToExtension()
}
}

private func forwardRequestToExtension() {
guard let receivedRequest = receivedRequest, let bestAttemptContent = bestAttemptContent else { return }
OneSignalExtension.didReceiveNotificationExtensionRequest(receivedRequest, with: bestAttemptContent, withContentHandler: contentHandler)
}

override func serviceExtensionTimeWillExpire() {
guard let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent else { return }
OneSignalExtension.serviceExtensionTimeWillExpireRequest(receivedRequest!, with: bestAttemptContent)
contentHandler(bestAttemptContent)
}
}

Storing OneSignal Tokens for each user

When a user allows notifications, OneSignal automatically assigns them a push subscription ID. In order to send notifications to specific users, you need to link their push subscription ID to their user in your database. If users are able to sign i on multiple devices, this should be stored as an array of tokens. Here is an example using Supabase.

const updateTokens = async (user: User, token: string) => {
try {
if (token) {
const { data: userData } = await supabase
.from("push_subscription_tokens")
.select()
.eq("uid", user.id);

if (userData) {
let tokens: string[] = userData[0]?.tokens || [];

if (!tokens.includes(token)) {
tokens.push(token);

const { data } = await supabase
.from("push_subscription_tokens")
.upsert({ uid: user.id, tokens });
}
}
}
} catch (error) {
console.error("Error updating tokens:", error);
}
};

When a user signs out on a device, you need to remove the push subscription ID for the device so you don’t send that user’s notifications to it anymore. Here is an example for that using the same Supabase structure from before:

export const removeToken = async (uid: string) => {
try {
const token = OneSignal.User.pushSubscription.getPushSubscriptionId();

const { data: userData } = await supabase
.from("push_subscription_tokens")
.select()
.eq("uid", uid);

if (userData) {
let tokens: string[] = userData[0]?.tokens || [];

if (tokens.includes(token)) {
const index = tokens.indexOf(token);
tokens.splice(index, 1);

const { data, error } = await supabase
.from("push_tokens")
.upsert({ uid: uid, tokens });
}
}
} catch (error) {
console.error("Error removing token:", error);
}
};

Sending a Communication Notification via the OneSignal REST API

When sending a notification to a specific user, you need to provide an array of their push subscription IDs. Luckily we stored those and linked them to the user’s uid in the previous section. Here is an example of sending a notification to a specific user, including the extra data for devices that are capable of Communication Notifications.

export async function sendPushNotification(
toUid: string, // Uid of recipient used to find their push subscription IDs
fromName: string, // Sender name
fromPhoto: string, // Sender profile picture
) {
const { data: tokensData } = await supabase
.from("push_subscription_tokens")
.select()
.eq("uid", toUid);

if (tokensData && tokensData[0]) {
const tokens = tokensData[0].tokens;

try {
const notification = {
app_id: <appId>, // Same appId from OneSignal.initialize()
include_subscription_ids: tokens,
headings: {
en: fromName, // Notification title
},
contents: {
en: "Sent you a message", // Notification body
},
data: {
// Sender name used for Communication Notification
name: fromName,
// Sender profile picture used for Communication Notification
url: fromPhoto,
},
};

const response = await fetch(
"https://onesignal.com/api/v1/notifications",
{
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
// Find Rest API Key in OneSignal App->Settings->Keys&IDs->Rest API Key
Authorization: `Basic <Rest API Key>`,
},
body: JSON.stringify(notification),
}
);
} catch (error) {
console.error("Error sending push notification:", error);
}
}
}

The end

You should now be able to send and display Communication Notifications for you React Native app on iOS. Below is an example Communication Notification that was created using the same code I shared. I hope this helps you, and feel free to comment any questions!

--

--