Entwickler · API v1.0.0

Expo-Integration

Dieser Leitfaden richtet sich an Apps, die mit Expo (Managed Workflow oder Development Build) entwickelt werden. Die allgemeine API-Referenz finden Sie in der Überblick.

Unterstützte Versionen: Expo SDK 50+ (empfohlen: aktuelles SDK) · iOS 13.4+ · Android 7.0+ (API 24)

Remote-Push-Benachrichtigungen funktionieren nicht in Expo Go (unter Android seit SDK 53 entfernt). Verwenden Sie zum Testen einen Development Build oder einen mit EAS Build erstellten Build. Push-Benachrichtigungen sollten nicht im Emulator/Simulator, sondern auf einem physischen Gerät getestet werden.

1. Installation

Terminal
npx expo install expo-notifications expo-device expo-constants expo-secure-store expo-localization expo-crypto

app.json / app.config.js:

JSON
{
  "expo": {
    "plugins": [
      ["expo-notifications", { "defaultChannel": "default" }]
    ],
    "extra": {
      "eas": { "projectId": "IHRE-EAS-PROJEKT-ID" }
    }
  }
}

Laden Sie die Push-Zugangsdaten in Ihr Expo-Projekt hoch (einmalig):

Terminal
eas credentials   # Android: JSON des FCM-V1-Dienstkontos, iOS: APNs Key

2. NotiPilot-Client

src/notipilot.ts:

TypeScript
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import * as SecureStore from 'expo-secure-store';
import * as Localization from 'expo-localization';
import * as Crypto from 'expo-crypto';
import Constants from 'expo-constants';
import { Platform } from 'react-native';

const NOTIPILOT_BASE_URL = 'https://app.notipilot.com/api/v1';

// Muss mit der "Expo Project ID" im NotiPilot-Dashboard übereinstimmen
const PROJECT_ID: string =
  Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId;

const DEVICE_UID_KEY = 'notipilot_device_uid';

// Unter SDK 52 und älter statt shouldShowBanner/shouldShowList shouldShowAlert: true verwenden
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true,
    shouldShowList: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

async function getDeviceUid(): Promise<string> {
  let uid = await SecureStore.getItemAsync(DEVICE_UID_KEY);
  if (!uid) {
    uid = Crypto.randomUUID();
    await SecureStore.setItemAsync(DEVICE_UID_KEY, uid);
  }
  return uid;
}

async function post(path: string, body: object, attempt = 0): Promise<any> {
  const res = await fetch(`${NOTIPILOT_BASE_URL}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify(body),
  });
  const json = await res.json().catch(() => ({}));

  if ((res.status === 429 || res.status >= 500) && attempt < 3) {
    const waitSec = Number(json.retry_after) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, waitSec * 1000));
    return post(path, body, attempt + 1);
  }
  if (!res.ok) {
    console.warn('[NotiPilot]', res.status, json.error, json.errors ?? json.message);
  }
  return json;
}

async function getExpoPushToken(): Promise<string | null> {
  if (!Device.isDevice) return null;

  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'Allgemein',
      importance: Notifications.AndroidImportance.HIGH,
    });
  }

  let { status } = await Notifications.getPermissionsAsync();
  if (status !== 'granted') {
    ({ status } = await Notifications.requestPermissionsAsync());
  }
  if (status !== 'granted') return null;

  const { data } = await Notifications.getExpoPushTokenAsync({ projectId: PROJECT_ID });
  return data; // "ExponentPushToken[...]"
}

export type NotiPilotAttributes = Record<string, string | number | boolean | null>;

/** Bei jedem App-Start aufrufen. */
export async function registerDevice(extra: NotiPilotAttributes = {}) {
  const token = await getExpoPushToken();
  if (!token) return null;

  const locale = Localization.getLocales()[0];

  return post('/register-device', {
    app_id: PROJECT_ID,
    device_uid: await getDeviceUid(),
    token,
    platform: Platform.OS, // 'ios' | 'android'
    provider: 'expo',
    attributes: {
      locale: locale?.languageCode ?? null,   // "tr"
      country: locale?.regionCode ?? null,    // "TR"
      app_version: Constants.expoConfig?.version ?? null,
      ...extra,                               // z. B. { city: 'Istanbul' }
    },
  });
}

/** Aufrufen, wenn sich der Nutzer anmeldet. */
export async function identify(externalId: string, attributes?: NotiPilotAttributes) {
  return post('/identify-device', {
    app_id: PROJECT_ID,
    device_uid: await getDeviceUid(),
    external_id: externalId,
    ...(attributes ? { attributes } : {}),
  });
}

/** Aufrufen, wenn sich der Nutzer abmeldet. */
export async function logout() {
  const token = await getExpoPushToken();
  if (!token) return null;
  return post('/register-device', {
    app_id: PROJECT_ID,
    device_uid: await getDeviceUid(),
    token,
    platform: Platform.OS,
    external_id: null,
  });
}

/** Hält NotiPilot bei Token-Erneuerung aktuell. Einmal beim App-Start aufrufen. */
export function listenForTokenChanges() {
  return Notifications.addPushTokenListener(() => {
    registerDevice().catch(() => {});
  });
}

3. Verwendung

TSX
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { registerDevice, listenForTokenChanges, identify } from './src/notipilot';

export default function App() {
  useEffect(() => {
    registerDevice({ city: 'Istanbul' }).catch(console.warn);
    const tokenSub = listenForTokenChanges();

    // Beim Tippen auf die Benachrichtigung das vom Dashboard gesendete Feld `data` auslesen
    const tapSub = Notifications.addNotificationResponseReceivedListener((response) => {
      const data = response.notification.request.content.data;
      // z. B. data.screen === 'product' → navigation.navigate('Product', { id: data.product_id })
    });

    return () => {
      tokenSub.remove();
      tapSub.remove();
    };
  }, []);

  // Nach dem Login: await identify(user.id, { gender: user.gender });
  return null;
}

4. Checkliste

  • Expo Project ID im Dashboard = extra.eas.projectId
  • FCM-V1- und APNs-Zugangsdaten mit eas credentials hochgeladen
  • Auf einem physischen Gerät mit Development-/Production-Build getestet
  • country und nach Möglichkeit city werden gesendet (für Standortsegmente)
  • Nach dem Login wird identify, beim Logout logout aufgerufen

Häufige Probleme

Symptom Lösung
404 unknown_app app_id ist im Dashboard nicht registriert oder falsch. Prüfen Sie die Expo Project ID im Dashboard.
Token kann nicht abgerufen werden Verwenden Sie ein physisches Gerät, prüfen Sie die Benachrichtigungsberechtigung und stellen Sie sicher, dass Sie den Parameter projectId übergeben.
Registrierung erfolgreich, aber keine Benachrichtigung Im Expo-Projekt fehlen möglicherweise die FCM-V1- / APNs-Zugangsdaten. Senden Sie mit dem Expo-Push-Tool eine Testbenachrichtigung an den Token.
Benachrichtigungen kommen unter Android stumm an Legen Sie einen Benachrichtigungskanal mit der ID default und der Wichtigkeitsstufe HIGH an.