cometchat-ios-push — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-ios-push (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ground truth:CometChatUIKitSwift ~> 5(+CometChatCallsSDK ~> 5) — Pods/SPM.swiftinterface+ui-kit/ios. Official docs: https://www.cometchat.com/docs/notifications/overview · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.
This skill teaches how to set up push notifications for CometChat iOS apps, including APNs configuration, token registration, and handling incoming notifications.
Before setting up push notifications:
.p8 file (you can only download it once!).p8 fileAdd the following to your Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>voip</string>
</array>import UIKit
import UserNotifications
import CometChatUIKitSwift
import CometChatSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize CometChat
initializeCometChat()
// Request notification permissions
requestNotificationPermission()
// Register for remote notifications
application.registerForRemoteNotifications()
return true
}
private func initializeCometChat() {
let uiKitSettings = UIKitSettings()
.set(appID: "YOUR_APP_ID")
.set(authKey: "YOUR_AUTH_KEY")
.set(region: "us")
.subscribePresenceForAllUsers()
.build()
CometChatUIKit(uiKitSettings: uiKitSettings) { result in
switch result {
case .success:
print("CometChat initialized")
case .failure(let error):
print("CometChat init failed: \(error)")
}
}
}
private func requestNotificationPermission() {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
print("Notification permission granted")
} else if let error = error {
print("Notification permission error: \(error)")
}
}
}
// MARK: - Remote Notification Registration
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("APNs Device Token: \(token)")
// Register token with CometChat
registerPushToken(token)
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register for remote notifications: \(error)")
}
private func registerPushToken(_ token: String) {
// CANONICAL v5 API: CometChatNotifications.registerPushToken takes a
// PushPlatforms enum (NOT a settings dict). Use .APNS_IOS_DEVICE for a
// standard APNs device token, .APNS_IOS_VOIP for a PushKit VoIP token,
// or .FCM_IOS if you route iOS through FCM. providerId is your dashboard
// push-provider id (pass nil to use the default).
CometChatNotifications.registerPushToken(
pushToken: token,
platform: .APNS_IOS_DEVICE,
providerId: nil
) { success in
print("Push token registered: \(success)")
} onError: { error in
print("Push token registration failed: \(error.errorDescription)")
}
// NOTE: the legacy `CometChat.registerTokenForPushNotification(token:settings:)`
// (v4-era) still compiles but is superseded — prefer the call above.
}
// MARK: - Token lifecycle (register AFTER login, unregister BEFORE logout)
//
// ⚠️ `didRegisterForRemoteNotifications` can fire before the user logs in.
// Registering a token that isn't bound to a CometChat session is a no-op at
// best. Stash the latest APNs token and register it only once login resolves:
//
// var pendingPushToken: String? // set in didRegisterForRemoteNotifications
// // after CometChatUIKit.login(...) onSuccess:
// if let t = pendingPushToken { registerPushToken(t) }
//
// And ALWAYS unregister before logout — otherwise the device keeps receiving
// pushes for the previous user:
func unregisterPushToken(_ completion: @escaping () -> Void) {
CometChatNotifications.unregisterPushToken { _ in
completion()
} onError: { _ in
completion() // proceed with logout regardless
}
}
// Usage at logout:
// unregisterPushToken {
// CometChatUIKit.logout(onSuccess: { _ in /* route to login */ }, onError: { _ in })
// }
// MARK: - Handle Incoming Notifications
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
print("Received remote notification: \(userInfo)")
// Handle CometChat notification
if let messageData = userInfo["message"] as? [String: Any] {
handleCometChatNotification(messageData)
}
completionHandler(.newData)
}
private func handleCometChatNotification(_ data: [String: Any]) {
// Parse notification data
guard let type = data["type"] as? String else { return }
switch type {
case "chat":
handleChatNotification(data)
case "call":
handleCallNotification(data)
default:
print("Unknown notification type: \(type)")
}
}
private func handleChatNotification(_ data: [String: Any]) {
// Extract sender info
guard let senderUID = data["sender"] as? String else { return }
// Navigate to conversation
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .openConversation,
object: nil,
userInfo: ["uid": senderUID]
)
}
}
private func handleCallNotification(_ data: [String: Any]) {
// Handle incoming call notification
print("Incoming call notification")
}
}
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
// Handle notification when app is in foreground
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
// Show notification even when app is in foreground
completionHandler([.banner, .sound, .badge])
}
// Handle notification tap
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if let messageData = userInfo["message"] as? [String: Any] {
handleCometChatNotification(messageData)
}
completionHandler()
}
}
// MARK: - Notification Names
extension Notification.Name {
static let openConversation = Notification.Name("openConversation")
}class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Listen for notification navigation
NotificationCenter.default.addObserver(
self,
selector: #selector(handleOpenConversation(_:)),
name: .openConversation,
object: nil
)
}
@objc private func handleOpenConversation(_ notification: Notification) {
guard let uid = notification.userInfo?["uid"] as? String else { return }
// Fetch user and open conversation
CometChat.getUser(UID: uid) { [weak self] user in
guard let user = user else { return }
DispatchQueue.main.async {
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer
messagesVC.set(user: user)
self?.navigationController?.pushViewController(messagesVC, animated: true)
}
} onError: { error in
print("Error fetching user: \(error?.errorDescription ?? "")")
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}import PushKitclass AppDelegate: UIResponder, UIApplicationDelegate {
var voipRegistry: PKPushRegistry?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// ... other setup ...
// Register for VoIP push
registerForVoIPPush()
return true
}
private func registerForVoIPPush() {
voipRegistry = PKPushRegistry(queue: .main)
voipRegistry?.delegate = self
voipRegistry?.desiredPushTypes = [.voIP]
}
}
// MARK: - PKPushRegistryDelegate
extension AppDelegate: PKPushRegistryDelegate {
func pushRegistry(
_ registry: PKPushRegistry,
didUpdate pushCredentials: PKPushCredentials,
for type: PKPushType
) {
let token = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
print("VoIP Token: \(token)")
// Register VoIP token with CometChat — canonical v5 API uses the
// .APNS_IOS_VOIP platform (NOT settings: ["voip": true]).
CometChatNotifications.registerPushToken(
pushToken: token,
platform: .APNS_IOS_VOIP,
providerId: nil
) { success in
print("VoIP token registered: \(success)")
} onError: { error in
print("VoIP token registration failed: \(error.errorDescription)")
}
}
func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void
) {
print("Received VoIP push: \(payload.dictionaryPayload)")
// Handle incoming call
if let callData = payload.dictionaryPayload["call"] as? [String: Any] {
handleIncomingCall(callData)
}
completion()
}
private func handleIncomingCall(_ data: [String: Any]) {
// Show incoming call UI
// This should use CallKit for proper iOS call handling
}
}You can also pass tokens during initialization:
let uiKitSettings = UIKitSettings()
.set(appID: "YOUR_APP_ID")
.set(authKey: "YOUR_AUTH_KEY")
.set(region: "us")
.set(deviceToken: apnsToken) // APNs token
.set(voipToken: voipToken) // VoIP token
.subscribePresenceForAllUsers()
.build()
CometChatUIKit(uiKitSettings: uiKitSettings) { result in
// ...
}For rich notifications with images and custom content:
// NotificationService.swift
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
contentHandler(request.content)
return
}
// Customize notification content
if let messageData = request.content.userInfo["message"] as? [String: Any] {
customizeNotification(bestAttemptContent, with: messageData)
}
contentHandler(bestAttemptContent)
}
private func customizeNotification(
_ content: UNMutableNotificationContent,
with data: [String: Any]
) {
// Set title from sender name
if let senderName = data["senderName"] as? String {
content.title = senderName
}
// Set body from message
if let messageText = data["text"] as? String {
content.body = messageText
}
// Add image attachment if available
if let imageURL = data["imageURL"] as? String,
let url = URL(string: imageURL) {
downloadAndAttachImage(url, to: content)
}
}
private func downloadAndAttachImage(_ url: URL, to content: UNMutableNotificationContent) {
let task = URLSession.shared.downloadTask(with: url) { localURL, _, error in
guard let localURL = localURL, error == nil else { return }
let tempDir = FileManager.default.temporaryDirectory
let tempFile = tempDir.appendingPathComponent(UUID().uuidString + ".jpg")
try? FileManager.default.moveItem(at: localURL, to: tempFile)
if let attachment = try? UNNotificationAttachment(identifier: "image", url: tempFile) {
content.attachments = [attachment]
}
self.contentHandler?(content)
}
task.resume()
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}// Set badge count
UIApplication.shared.applicationIconBadgeNumber = unreadCount
// Clear badge count
UIApplication.shared.applicationIconBadgeNumber = 0func updateBadgeCount() {
// getUnreadMessageCount's success closure receives a SINGLE [String: Any]
// (conversations keyed by uid/guid → count) — NOT two separate dicts.
CometChat.getUnreadMessageCount { unread in
var totalUnread = 0
for (_, count) in unread {
totalUnread += count as? Int ?? 0
}
DispatchQueue.main.async {
UIApplication.shared.applicationIconBadgeNumber = totalUnread
}
} onError: { error in
print("Error getting unread count: \(error?.errorDescription ?? "")")
}
}# Test APNs push (requires authentication)
curl -v \
--header "apns-topic: com.yourapp.bundleid" \
--header "apns-push-type: alert" \
--header "authorization: bearer $JWT_TOKEN" \
--data '{"aps":{"alert":"Test message"}}' \
--http2 \
https://api.push.apple.com/3/device/$DEVICE_TOKEN| Issue | Solution |
|---|---|
| Token not registering | Ensure device is physical, not simulator |
| Notifications not received | Check APNs certificate in dashboard |
| Badge not updating | Check notification permissions |
| VoIP not working | Ensure VoIP capability is enabled |
| Notifications delayed | Check APNs environment (dev vs prod) |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.