Documentation

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

  1. Va sur dashboard.onesignal.comSign up (gratuit)
  2. New App/Website → choisis Web comme plateforme
  3. 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
  4. 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 :

  1. Dashboard OneSignal → Settings → Push & In-App
  2. Permission Prompt Setup (ou onglet Prompts)
  3. Désactive le toggle du Slidedown (laisse uniquement le native browser prompt activé)
  4. Save

5. Composants web installés

FichierRôle
lib/onesignal.tsHelper server-side sendPushNotification
components/providers/onesignal-provider.tsxCharge le SDK Web + login user après auth
components/providers/push-prompt.tsxUI custom (card design system)
public/OneSignalSDKWorker.jsService 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-https

B. Tester sur un staging HTTPS (Vercel preview deploy, etc.)

Une fois sur HTTPS :

  1. Connecte-toi avec un compte
  2. Accepte le cookie consent
  3. Attends 2 secondes → prompt custom en bas-droite
  4. Clique "Activer" → autorise les notifications
  5. 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

  1. Va sur console.firebase.google.comAjouter un projet
  2. Nom du projet (ex: nextvault-mobile) → Continue
  3. Désactive Google Analytics si tu ne l'utilises pas → Créer le projet

Étape 2 — Ajouter l'app Android

  1. Dans le projet Firebase, clique "Ajouter une app" → icône Android
  2. Package Android : doit correspondre exactement à ton app.json → champ android.package (ex: com.nextvault.mobile)
  3. Surnom : optionnel
  4. Clique "Enregistrer l'app"
  5. Télécharge google-services.json → place-le à la racine du projet mobile (test/mobile/google-services.json)
  6. 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

  1. Dans Firebase Console → ⚙️ Paramètres du projet → onglet Cloud Messaging
  2. L'API Firebase Cloud Messaging (V1) doit être Activée
  3. Clique "Gérer les comptes de service" → Google Cloud Console s'ouvre
  4. Trouve le compte firebase-adminsdk-...@<project>.iam.gserviceaccount.com
  5. ⋮ → Gérer les clés → Ajouter une clé → Créer une clé → JSON → télécharge le fichier
  6. Dans OneSignal → Settings → Push & In-App → Platforms → Google Android
  7. Choisis "React Native / Expo" puis Firebase Cloud Messaging V1
  8. 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:android

Tester en local

  1. Après le rebuild, ouvre l'app → accepte le dialog de permission
  2. Connecte-toi avec un compte
  3. Vérifie dans OneSignal → Audience → Subscriptions : le device doit apparaître avec le statut Subscribed
  4. 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
});
  • userIds est ciblé via include_aliases: { external_id: ... } — map directement sur user.id Prisma
  • Best-effort : un échec push ne casse pas le flow appelant

Debug

SymptômeCause probable
Device absent des SubscriptionsinitOneSignal 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 logsEXPO_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 çaRebuild natif complet (rm -rf android && npx expo run:android) — les changements app.json ne sont pas pris sans rebuild
Invalid app_id (400) webONESIGNAL_APP_ID mauvais
Authentication required (401) webONESIGNAL_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_KEY est server-only — jamais exposée au client
  • NEXT_PUBLIC_ONESIGNAL_APP_ID / EXPO_PUBLIC_ONESIGNAL_APP_ID sont 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)

On this page