Mobile Push Notifications
Интеграция FCM / APNs / Web Push с NurCore — register tokens + handle push events.
NurCore использует Firebase Cloud Messaging (FCM) для всех push-каналов:
- iOS — через FCM (FCM сам общается с APNs)
- Android — нативный FCM
- Web — FCM Web Push (с VAPID)
Mobile / web client отвечает за получение FCM token от Firebase SDK
и регистрацию его в NurCore через /devices/register. NurCore хранит
token и при необходимости рассылает push через FCM HTTP v1 API.
Flow
1. App startup → Firebase SDK getToken({ vapidKey?: ... })
2. После passenger login → POST /devices/register с FCM token
3. App in background → FCM доставляет push через OS
4. App in foreground → SDK callback, app показывает in-app notification
5. User logout → DELETE /devices/{token}React Native (recommended)
Install
npx expo install @react-native-firebase/app @react-native-firebase/messagingiOS: добавьте GoogleService-Info.plist в ios/. Android: добавьте google-services.json в android/app/. Эти файлы выдаются вместе с API ключами для вашей mobile-app интеграции — свяжитесь с developers@nurcore.kg.
Permission + getToken
import messaging from "@react-native-firebase/messaging";
async function setupPush() {
// iOS требует явного разрешения, Android (< 13) — нет
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (!enabled) return null;
const token = await messaging().getToken();
return token;
}Register token в NurCore
async function registerDevice(jwtAccessToken: string, fcmToken: string, platform: "ios" | "android") {
const res = await fetch("https://api.nurcore.kg/api/v1/auth/passenger/devices/register", {
method: "POST",
headers: {
"Authorization": `Bearer ${jwtAccessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
platform,
fcm_token: fcmToken,
device_id: "com.example.airline-app", // ваш bundle ID
app_version: "1.0.0",
locale: "ru-RU",
}),
});
if (!res.ok) throw new Error("Failed to register device");
return res.json();
}Handlers
// Foreground (app открыто) — показать toast / banner
messaging().onMessage(async (remoteMessage) => {
console.log("Push received in foreground:", remoteMessage);
// Показать локальное уведомление через notifee или встроенный UI
});
// Background / killed — handler для navigation после tap
messaging().onNotificationOpenedApp((remoteMessage) => {
console.log("Push tapped (app was in background):", remoteMessage);
if (remoteMessage.data?.type === "flight_delayed") {
navigation.navigate("FlightDetail", { flightId: remoteMessage.data.flight_id });
}
});
// Killed app — initial check
messaging().getInitialNotification().then((remoteMessage) => {
if (remoteMessage) {
// App был запущен tap'ом по push
}
});Token refresh
Firebase может ротировать token (раз в несколько недель). Подписывайтесь:
messaging().onTokenRefresh((newToken) => {
registerDevice(jwtAccessToken, newToken, platform);
});Logout
async function logoutAndUnregister(jwtAccessToken: string, fcmToken: string) {
await fetch(`https://api.nurcore.kg/api/v1/auth/passenger/devices/${fcmToken}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${jwtAccessToken}` },
});
await messaging().deleteToken(); // FCM сам удалит token у Google
}Flutter
# pubspec.yaml
dependencies:
firebase_core: ^3.0.0
firebase_messaging: ^15.0.0import 'package:firebase_messaging/firebase_messaging.dart';
Future<void> setupPush() async {
await FirebaseMessaging.instance.requestPermission();
final token = await FirebaseMessaging.instance.getToken();
// Register в NurCore
await http.post(
Uri.parse('https://api.nurcore.kg/api/v1/auth/passenger/devices/register'),
headers: {
'Authorization': 'Bearer $jwtAccessToken',
'Content-Type': 'application/json',
},
body: jsonEncode({
'platform': Platform.isIOS ? 'ios' : 'android',
'fcm_token': token,
'device_id': 'com.example.airline',
'locale': 'ru-RU',
}),
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Foreground handler
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// Tap handler
});
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
// Re-register с новым token
});
}Native iOS (Swift)
import FirebaseMessaging
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
if granted {
DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
}
}
Messaging.messaging().token { token, error in
guard let token = token else { return }
// POST к /devices/register с platform: "ios"
}Native Android (Kotlin)
// build.gradle (project)
classpath 'com.google.gms:google-services:4.4.0'
// build.gradle (app)
apply plugin: 'com.google.gms.google-services'
implementation 'com.google.firebase:firebase-messaging:24.0.0'FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) return@addOnCompleteListener
val token = task.result
// POST к /devices/register с platform: "android"
}Web Push
<!-- Загрузить Firebase SDK -->
<script src="https://www.gstatic.com/firebasejs/10.0.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.0.0/firebase-messaging-compat.js"></script>const firebaseConfig = {
apiKey: "YOUR_API_KEY",
projectId: "your-project",
messagingSenderId: "...",
appId: "...",
};
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
const VAPID_KEY = "YOUR_VAPID_PUBLIC_KEY"; // выдаётся NurCore
const token = await messaging.getToken({ vapidKey: VAPID_KEY });
// Register в NurCore
await fetch("https://api.nurcore.kg/api/v1/auth/passenger/devices/register", {
method: "POST",
headers: {
"Authorization": `Bearer ${jwtAccessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
platform: "web",
fcm_token: token,
device_id: navigator.userAgent.slice(0, 100),
locale: navigator.language,
}),
});
// Foreground handler
messaging.onMessage((payload) => {
new Notification(payload.notification.title, {
body: payload.notification.body,
});
});firebase-messaging-sw.js (service worker, обязательно):
importScripts("https://www.gstatic.com/firebasejs/10.0.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.0.0/firebase-messaging-compat.js");
firebase.initializeApp({
apiKey: "...", projectId: "...", messagingSenderId: "...", appId: "...",
});
firebase.messaging();Endpoints
POST /api/v1/auth/passenger/devices/register
Создать или обновить device token. Upsert by fcm_token.
Headers:
Authorization: Bearer <passenger_jwt>— passenger JWT (scope=passenger)
Body:
{
"platform": "ios",
"fcm_token": "<FCM token from Firebase SDK>",
"device_id": "com.airline.app",
"app_version": "1.0.0",
"locale": "ru-RU"
}DELETE /api/v1/auth/passenger/devices/{fcm_token}
Soft-delete (is_active=False). Используется при logout.
GET /api/v1/auth/passenger/devices/me
Список активных устройств (для UI "Мои устройства").
Push payload format
NurCore отправляет push в стандартном FCM формате:
{
"notification": {
"title": "Рейс MN-001 задержан",
"body": "Новое время вылета: 14:30"
},
"data": {
"type": "flight_delayed",
"flight_id": "uuid",
"flight_number": "MN-001",
"delay_minutes": "45"
}
}Используйте data.type для роутинга в приложении (deep link).
Поддерживаемые события
Список push events которые NurCore отправляет (Phase 4 launch):
booking_confirmed— после оплаты бронейflight_delayed— задержка ≥ 30 минflight_gate_changed— изменение гейтаflight_cancelled— отмена рейсаboarding_open— началась посадкаcheckin_open— открыта регистрация (T-24h)
Поддержка
- B2B push integration:
partners@nurcore.kg - Mobile SDK questions:
developers@nurcore.kg