NurCore API
Guides

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}

Install

npx expo install @react-native-firebase/app @react-native-firebase/messaging

iOS: добавьте 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.0
import '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

On this page