notifications — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited notifications (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.
bunx expo install expo-notifications expo-device expo-constantsapp.json:
{
"expo": {
"plugins": [
["expo-notifications", {
"icon": "./assets/notification-icon.png",
"color": "#6366f1",
"sounds": ["./assets/notification.wav"]
}]
],
"ios": { "bundleIdentifier": "com.myorg.myapp" },
"android": { "googleServicesFile": "./google-services.json" }
}
}// src/libs/notifications.ts
import * as Device from 'expo-device'
import * as Notifications from 'expo-notifications'
import Constants from 'expo-constants'
import { Platform } from 'react-native'
export async function registerForPushNotifications(): Promise<string | null> {
if (!Device.isDevice) {
console.warn('Push notifications require a physical device')
return null
}
const { status: existing } = await Notifications.getPermissionsAsync()
let finalStatus = existing
if (existing !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') return null
const projectId = Constants.expoConfig?.extra?.eas?.projectId
const token = await Notifications.getExpoPushTokenAsync({ projectId })
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
})
}
return token.data
}Set in root _layout.tsx before any component renders:
import * as Notifications from 'expo-notifications'
// How to display notifications when app is in foreground
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
})
export default function RootLayout() {
// ...
}src/hooks/use-notifications.ts)import { useEffect, useRef } from 'react'
import * as Notifications from 'expo-notifications'
import { router } from 'expo-router'
import type { Href } from 'expo-router'
import { registerForPushNotifications } from '@/libs/notifications'
export function useNotifications() {
const notificationListener = useRef<Notifications.EventSubscription>()
const responseListener = useRef<Notifications.EventSubscription>()
useEffect(() => {
registerForPushNotifications().then(token => {
if (token) {
// Send token to your API
}
})
// Fires when notification received in foreground
notificationListener.current = Notifications.addNotificationReceivedListener(_notification => {
// handle foreground notification -- e.g. update badge count or show in-app toast
})
// Fires when user taps notification
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
const data = response.notification.request.content.data
if (data.screen) {
// notification payload is untyped -- validate screen values server-side before sending
router.push(data.screen as Href)
}
})
return () => {
notificationListener.current?.remove()
responseListener.current?.remove()
}
}, [])
}Use in root _layout.tsx:
export default function RootLayout() {
useNotifications()
// ...
}import * as Notifications from 'expo-notifications'
import { useEffect } from 'react'
import { router } from 'expo-router'
import type { Href } from 'expo-router'
export default function RootLayout() {
useEffect(() => {
// App opened from a notification
Notifications.getLastNotificationResponseAsync().then(response => {
const screen = response?.notification.request.content.data.screen
if (screen) {
// cold-start navigation from notification -- payload is untyped, validate screen values server-side
router.push(screen as Href)
}
})
}, [])
}// Schedule a local notification
await Notifications.scheduleNotificationAsync({
content: {
title: 'Reminder',
body: 'Don\'t forget to check in!',
data: { screen: '/(app)/(tabs)/' },
},
trigger: { seconds: 60 }, // 1 minute from now
})
// Cancel all scheduled
await Notifications.cancelAllScheduledNotificationsAsync()useEffect returngetLastNotificationResponseAsync for cold-start navigationsetNotificationHandler to control displaygoogle-services.json must be in EAS secrets for CI -- never committed~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.