.agents/skills/push-notification-setup/SKILL.md
Implement push notifications for iOS and Android. Covers Firebase Cloud Messaging, Apple Push Notification service, handling notifications, and backend integration.
npx skillsauth add hillyattic/jpco push-notification-setupInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement comprehensive push notification systems for iOS and Android applications using Firebase Cloud Messaging and native platform services.
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
export async function initializeFirebase() {
try {
if (Platform.OS === 'ios') {
const permission = await messaging().requestPermission();
if (permission === messaging.AuthorizationStatus.AUTHORIZED) {
console.log('iOS notification permission granted');
}
}
const token = await messaging().getToken();
console.log('FCM Token:', token);
await saveTokenToBackend(token);
messaging().onTokenRefresh(async (newToken) => {
await saveTokenToBackend(newToken);
});
messaging().onMessage(async (remoteMessage) => {
console.log('Notification received:', remoteMessage);
showLocalNotification(remoteMessage);
});
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
if (remoteMessage.data?.type === 'sync') {
syncData();
}
});
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
handleNotificationOpen(remoteMessage);
}
});
messaging().onNotificationOpenedApp((remoteMessage) => {
handleNotificationOpen(remoteMessage);
});
} catch (error) {
console.error('Firebase initialization failed:', error);
}
}
export async function saveTokenToBackend(token) {
try {
const response = await fetch('https://api.example.com/device-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
platform: Platform.OS,
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
console.error('Failed to save token');
}
} catch (error) {
console.error('Error saving token:', error);
}
}
function handleNotificationOpen(remoteMessage) {
const { data } = remoteMessage;
if (data?.deepLink) {
navigationRef.navigate(data.deepLink, JSON.parse(data.params || '{}'));
}
}
import UIKit
import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
requestNotificationPermission()
if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
handlePushNotification(remoteNotification)
}
return true
}
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("Device Token: \(token)")
saveTokenToBackend(token: token)
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register: \(error)")
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void
) {
let userInfo = notification.request.content.userInfo
if #available(iOS 14.0, *) {
completionHandler([.banner, .sound, .badge])
} else {
completionHandler([.sound, .badge])
}
handlePushNotification(userInfo)
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
handlePushNotification(userInfo)
completionHandler()
}
private func handlePushNotification(_ userInfo: [AnyHashable: Any]) {
if let deepLink = userInfo["deepLink"] as? String {
NotificationCenter.default.post(
name: NSNotification.Name("openDeepLink"),
object: deepLink
)
}
}
private func saveTokenToBackend(token: String) {
let urlString = "https://api.example.com/device-tokens"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = ["token": token, "platform": "ios"]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request).resume()
}
}
// AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".services.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
println("FCM Token: $token")
saveTokenToBackend(token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
val title = remoteMessage.notification?.title ?: "Notification"
val body = remoteMessage.notification?.body ?: ""
val deepLink = remoteMessage.data["deepLink"] ?: ""
if (remoteMessage.notification != null) {
showNotification(title, body, deepLink)
}
}
private fun showNotification(title: String, message: String, deepLink: String = "") {
val channelId = "default_channel"
createNotificationChannel(channelId)
val intent = Intent(this, MainActivity::class.java).apply {
if (deepLink.isNotEmpty()) {
data = android.net.Uri.parse(deepLink)
}
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
}
private fun createNotificationChannel(channelId: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Default Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun saveTokenToBackend(token: String) {
println("Saving token to backend: $token")
}
}
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
class NotificationHandler {
static Future<void> initialize(NavigatorState navigator) async {
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
sound: true,
badge: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('Notification permission granted');
}
final token = await FirebaseMessaging.instance.getToken();
print('FCM Token: $token');
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Received: ${message.notification?.title}');
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_handleDeepLink(navigator, message.data);
});
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_handleDeepLink(navigator, initialMessage.data);
}
}
static void _handleDeepLink(NavigatorState navigator, Map<String, dynamic> data) {
final deepLink = data['deepLink'] as String?;
if (deepLink != null) {
navigator.pushNamed(deepLink);
}
}
}
development
Generates Postman collection JSON files from Express, Next.js, Fastify, Hono, or other API routes. Scans route definitions, extracts endpoints, methods, params, and creates importable collections. Use when users request "generate postman collection", "export to postman", "create postman file", or "postman import".
tools
Use Postman MCP to inspect/maintain collections, environments, and API workflows; generate or validate requests and tests. Use when API verification or collections are the source of truth.
tools
A brief description of what this skill does
development
Generates Postman collection JSON files from Express, Next.js, Fastify, Hono, or other API routes. Scans route definitions, extracts endpoints, methods, params, and creates importable collections. Use when users request "generate postman collection", "export to postman", "create postman file", or "postman import".