Push notifications (OneSignal)
Notifications push web et mobile (Android/iOS) via OneSignal — Firebase FCM, setup pas-à-pas, helper unifié, intégration broadcasts.
Le boilerplate utilise OneSignal pour les notifications push web et mobile. Un seul helper server-side + un provider client web + un wrapper natif mobile.
Web
1. Créer un compte + une app OneSignal
- Va sur dashboard.onesignal.com → Sign up (gratuit)
- New App/Website → choisis Web comme plateforme
- Setup Web :
- Site Name : nom de ton app
- Site URL :
http://localhost:3000(ou ton port dev) en dev, ton domaine en prod - ⚠️ Coche "Local Testing" pour autoriser localhost
- Default Icon URL : optionnel
- Sauvegarde → tu arrives sur Settings → Keys & IDs
2. Récupérer les clés
Dans Settings → Keys & IDs :
- OneSignal App ID (format UUID
xxxxxxxx-xxxx-...) → sert pour 2 variables - REST API Key (commence par
os_v2_app_...)
3. Variables d'environnement
Dans .env :
NEXT_PUBLIC_ONESIGNAL_APP_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
ONESIGNAL_APP_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # même valeur
ONESIGNAL_REST_API_KEY="os_v2_app_..."⚠️ NEXT_PUBLIC_* est bakée au build → redémarre npm run dev après modification.
Sans ces variables, sendPushNotification est un no-op silencieux et le provider client ne s'initialise pas — le boilerplate marche en local sans config.
4. Désactiver le prompt natif OneSignal
Le boilerplate fournit son propre prompt qui match le design system (card en bas-droite, animation, boutons "Activer / Plus tard"). Il faut désactiver le Slidedown OneSignal pour éviter le double prompt :
- Dashboard OneSignal → Settings → Push & In-App
- Permission Prompt Setup (ou onglet Prompts)
- Désactive le toggle du Slidedown (laisse uniquement le native browser prompt activé)
- Save
5. Composants web installés
| Fichier | Rôle |
|---|---|
lib/onesignal.ts | Helper server-side sendPushNotification |
components/providers/onesignal-provider.tsx | Charge le SDK Web + login user après auth |
components/providers/push-prompt.tsx | UI custom (card design system) |
public/OneSignalSDKWorker.js | Service Worker (requis par OneSignal) |
messages/{fr,en}.json → clé push.* | Traductions du prompt |
Les deux providers sont déjà montés dans app/layout.tsx.
6. Tester en local
⚠️ OneSignal SDK v16 ne fonctionne plus en HTTP — même allowLocalhostAsSecureOrigin est ignoré. Pour tester en local, 2 options :
A. Lancer Next.js en HTTPS (recommandé) :
npm run dev -- --experimental-httpsB. Tester sur un staging HTTPS (Vercel preview deploy, etc.)
Une fois sur HTTPS :
- Connecte-toi avec un compte
- Accepte le cookie consent
- Attends 2 secondes → prompt custom en bas-droite
- Clique "Activer" → autorise les notifications
- Dashboard OneSignal → Audience → Subscriptions : le device apparaît
App mobile (Android)
Les notifications push mobiles nécessitent Firebase Cloud Messaging (FCM) — même si tu utilises OneSignal, Android passe obligatoirement par FCM pour délivrer les notifications.
Étape 1 — Créer un projet Firebase
- Va sur console.firebase.google.com → Ajouter un projet
- Nom du projet (ex:
nextvault-mobile) → Continue - Désactive Google Analytics si tu ne l'utilises pas → Créer le projet
Étape 2 — Ajouter l'app Android
- Dans le projet Firebase, clique "Ajouter une app" → icône Android
- Package Android : doit correspondre exactement à ton
app.json→ champandroid.package(ex:com.nextvault.mobile) - Surnom : optionnel
- Clique "Enregistrer l'app"
- Télécharge
google-services.json→ place-le à la racine du projet mobile (test/mobile/google-services.json) - Ignore les étapes suivantes (le SDK est déjà intégré via Expo)
Étape 3 — Configurer app.json
Dans app.json, ajoute googleServicesFile dans la section android ET le plugin expo-notifications :
{
"expo": {
"android": {
"package": "com.nextvault.mobile",
"googleServicesFile": "./google-services.json"
},
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/images/adaptive-icon.png",
"color": "#ffffff"
}
],
[
"onesignal-expo-plugin",
{ "mode": "production" }
]
]
}
}⚠️ Sans googleServicesFile, Expo ignore complètement le fichier et FCM ne s'initialise pas.
Étape 4 — Connecter FCM à OneSignal
- Dans Firebase Console → ⚙️ Paramètres du projet → onglet Cloud Messaging
- L'API Firebase Cloud Messaging (V1) doit être Activée
- Clique "Gérer les comptes de service" → Google Cloud Console s'ouvre
- Trouve le compte
firebase-adminsdk-...@<project>.iam.gserviceaccount.com - ⋮ → Gérer les clés → Ajouter une clé → Créer une clé → JSON → télécharge le fichier
- Dans OneSignal → Settings → Push & In-App → Platforms → Google Android
- Choisis "React Native / Expo" puis Firebase Cloud Messaging V1
- Upload le fichier JSON du compte de service → Sauvegarde
Étape 5 — Variables d'environnement mobile
Dans test/mobile/.env :
EXPO_PUBLIC_ONESIGNAL_APP_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Même App ID que le web (OneSignal gère les deux depuis la même app).
Étape 6 — Init dans useEffect (obligatoire)
OneSignal.initialize() doit être appelé dans un useEffect, pas au niveau module. Sur Android, il a besoin que l'Activity soit disponible :
// app/_layout.tsx
useEffect(() => {
const appId = process.env.EXPO_PUBLIC_ONESIGNAL_APP_ID ?? "";
initOneSignal(appId);
requestPushPermission().catch(() => {});
}, []);Étape 7 — Demander la permission (Android 13+)
Android 13+ requiert POST_NOTIFICATIONS explicitement. Le wrapper requestPushPermission() utilise expo-notifications qui gère correctement ce dialog :
// lib/onesignal.ts
export async function requestPushPermission(): Promise<boolean> {
const { requestPermissionsAsync, getPermissionsAsync } = await import("expo-notifications");
const existing = await getPermissionsAsync();
if (existing.granted) return true;
if (!existing.canAskAgain) return false;
const result = await requestPermissionsAsync();
if (result.granted) await _OneSignal?.Notifications.requestPermission(false);
return result.granted;
}Étape 8 — Associer le device à l'user
Après le login, lie le device à l'user OneSignal pour pouvoir le cibler par son ID :
// Après login réussi
loginOneSignal(user.id); // OneSignal.login(userId)
// Au logout
logoutOneSignal(); // OneSignal.logout()Étape 9 — Rebuild natif
⚠️ Tout changement dans app.json (plugins, googleServicesFile…) nécessite un rebuild natif complet — le JS bundle seul ne suffit pas :
cd test/mobile
# Premier build ou après changement app.json :
Remove-Item -Recurse -Force android # PowerShell
# ou : rm -rf android # bash
npx expo run:androidTester en local
- Après le rebuild, ouvre l'app → accepte le dialog de permission
- Connecte-toi avec un compte
- Vérifie dans OneSignal → Audience → Subscriptions : le device doit apparaître avec le statut Subscribed
- Messages → New Push → titre + message → Send to Test Device ou cibler par External ID
Helper server-side (web + mobile)
lib/onesignal.ts → sendPushNotification. Wrapper unifié — le même envoie aux users web ET mobile.
import { sendPushNotification } from "@/lib/onesignal";
await sendPushNotification({
userIds: ["user_id_1", "user_id_2"],
title: "Nouveau message",
message: "Tu as reçu une réponse.",
url: "https://app.com/messages/abc", // optionnel
data: { messageId: "abc" }, // optionnel
});userIdsest ciblé viainclude_aliases: { external_id: ... }— map directement suruser.idPrisma- Best-effort : un échec push ne casse pas le flow appelant
Debug
| Symptôme | Cause probable |
|---|---|
| Device absent des Subscriptions | initOneSignal appelé au niveau module (avant Activity prête) → déplacer dans useEffect |
| Status "Permission Not Granted" | requestPermissionsAsync() (expo-notifications) pas appelé, ou permission refusée dans les réglages Android |
| "no appId provided" dans les logs | EXPO_PUBLIC_ONESIGNAL_APP_ID vide ou non chargé |
| FCM non initialisé | google-services.json présent mais googleServicesFile absent de app.json → rebuild natif nécessaire |
| Toujours rien après tout ça | Rebuild natif complet (rm -rf android && npx expo run:android) — les changements app.json ne sont pas pris sans rebuild |
Invalid app_id (400) web | ONESIGNAL_APP_ID mauvais |
Authentication required (401) web | ONESIGNAL_REST_API_KEY mauvais ou format obsolète |
Intégration broadcasts
Voir Broadcasts. Quand l'admin envoie un broadcast avec le canal push, chaque user du segment reçoit un push individuel (web + mobile si configuré).
Sécurité
ONESIGNAL_REST_API_KEYest server-only — jamais exposée au clientNEXT_PUBLIC_ONESIGNAL_APP_ID/EXPO_PUBLIC_ONESIGNAL_APP_IDsont publiques (pas des secrets)- Le prompt web n'apparaît qu'après consentement cookies (RGPD)
- Pas de PII dans
data(remonte côté OneSignal en clair)