Entwickler · API v1.0.0
Ionic-Integration (Capacitor)
Dieser Leitfaden richtet sich an Android- und iOS-Apps, die mit Ionic + Capacitor entwickelt werden (egal ob mit Angular, React oder Vue). Die allgemeine API-Referenz finden Sie in der Überblick.
Unterstützte Versionen
| Komponente | Minimum | Empfohlen |
|---|---|---|
| Capacitor | 6 | aktuell |
@capacitor/push-notifications |
6.x | aktuell |
| Ionic Framework | 7 | aktuell |
| Android | 6.0 (API 23) | targetSdk 35+ |
| iOS | 13.0 | 15.0+ |
Ältere, Cordova-basierte Ionic-Projekte werden nicht offiziell unterstützt; ein Umstieg auf Capacitor wird empfohlen. PWA/Web Push wird in Version 1.0.0 nicht unterstützt.
Wie funktioniert es?
NotiPilot 1.0.0 stellt Benachrichtigungen über den Expo Push Service zu. Ihre App:
- ruft mit
@capacitor/push-notificationsden nativen Token ab (Android: FCM-Token, iOS: APNs-Token), - konvertiert diesen Token über den Expo-Token-Service in einen Expo Push Token,
- registriert den Expo Push Token mit
register-devicebei NotiPilot.
1. Vorbereitung (einmalig)
- Android: Fügen Sie Ihre Android-App in der Firebase-Konsole hinzu und legen Sie die Datei
google-services.jsoninandroid/app/ab. - iOS: Aktivieren Sie im Apple Developer Portal die Capability Push Notifications und erstellen Sie einen APNs-Schlüssel (
.p8) (Schritte: iOS-Leitfaden). - Legen Sie auf expo.dev ein Projekt an, notieren Sie die Projekt-ID und laden Sie im Bereich Credentials das JSON des FCM-V1-Dienstkontos (Android) sowie den APNs-Schlüssel (
.p8) (iOS) hoch. - Legen Sie Ihre App im NotiPilot-Dashboard an: Expo Project ID, Expo Access Token, Android Package, iOS Bundle ID (=
appIdincapacitor.config.ts).
2. Installation
npm install @capacitor/push-notifications @capacitor/preferences @capacitor/app @capacitor/device
npx cap sync
iOS: Fügen Sie in Xcode (npx cap open ios) unter App-Target → Signing & Capabilities die Capabilities Push Notifications und Background Modes → Remote notifications hinzu. Ergänzen Sie in ios/App/App/AppDelegate.swift Folgendes:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}
Android: In android/variables.gradle muss minSdkVersion = 23 gesetzt sein.
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
plugins: {
PushNotifications: { presentationOptions: ['badge', 'sound', 'alert'] },
},
};
3. src/notipilot.ts
import { Capacitor } from '@capacitor/core';
import { PushNotifications, type Token, type ActionPerformed } from '@capacitor/push-notifications';
import { Preferences } from '@capacitor/preferences';
import { App } from '@capacitor/app';
import { Device } from '@capacitor/device';
const NOTIPILOT_BASE_URL = 'https://app.notipilot.com/api/v1';
const APP_ID = 'IHRE-EXPO-PROJEKT-ID'; // "Expo Project ID" aus dem Dashboard
const UID_KEY = 'notipilot_device_uid';
type Attributes = Record<string, string | number | boolean | null>;
async function deviceUid(): Promise<string> {
const { value } = await Preferences.get({ key: UID_KEY });
if (value) return value;
const uid = crypto.randomUUID();
await Preferences.set({ key: UID_KEY, value: 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) {
await new Promise((r) => setTimeout(r, (Number(json.retry_after) || 2 ** attempt) * 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 toExpoPushToken(nativeToken: string, uid: string): Promise<string> {
const isIOS = Capacitor.getPlatform() === 'ios';
const { id: bundleId } = await App.getInfo();
const res = await fetch('https://exp.host/--/api/v2/push/getExpoPushToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: isIOS ? 'apns' : 'fcm',
deviceId: uid.toLowerCase(),
development: isIOS && import.meta.env.DEV, // iOS-Debug-Builds nutzen die APNs-Sandbox
appId: bundleId,
deviceToken: nativeToken,
projectId: APP_ID,
}),
});
if (!res.ok) throw new Error(`Expo token exchange failed: ${res.status}`);
return (await res.json()).data.expoPushToken;
}
/** Beim App-Start aufrufen. */
export async function initNotiPilot(extraAttributes: Attributes = {}) {
if (!Capacitor.isNativePlatform()) return;
await PushNotifications.addListener('registration', async (token: Token) => {
try {
const uid = await deviceUid();
const expoToken = await toExpoPushToken(token.value, uid);
const { languageCode } = await Device.getLanguageCode();
const { version } = await App.getInfo();
const region = new Intl.Locale(navigator.language).region ?? null;
await post('/register-device', {
app_id: APP_ID,
device_uid: uid,
token: expoToken,
platform: Capacitor.getPlatform(), // 'ios' | 'android'
provider: 'expo',
attributes: {
locale: languageCode.slice(0, 2), // "tr"
country: region, // "TR"
app_version: version,
...extraAttributes, // z. B. { city: 'Istanbul' }
},
});
} catch (e) {
console.warn('[NotiPilot] register failed', e);
}
});
await PushNotifications.addListener('registrationError', (err) =>
console.warn('[NotiPilot] native registration error', err),
);
// Auf Benachrichtigung getippt — vom Dashboard gesendete benutzerdefinierte Daten
await PushNotifications.addListener('pushNotificationActionPerformed', (action: ActionPerformed) => {
const raw = action.notification.data?.body;
const data = typeof raw === 'string' ? safeJson(raw) : raw ?? action.notification.data;
// z. B. data.screen === 'product' → router.navigate(...)
});
if (Capacitor.getPlatform() === 'android') {
await PushNotifications.createChannel({ id: 'default', name: 'Allgemein', importance: 5 });
}
let perm = await PushNotifications.checkPermissions();
if (perm.receive === 'prompt') perm = await PushNotifications.requestPermissions();
if (perm.receive === 'granted') await PushNotifications.register();
}
/** Aufrufen, wenn sich der Nutzer anmeldet. */
export async function identify(externalId: string, attributes?: Attributes) {
return post('/identify-device', {
app_id: APP_ID,
device_uid: await deviceUid(),
external_id: externalId,
...(attributes ? { attributes } : {}),
});
}
function safeJson(s: string) {
try { return JSON.parse(s); } catch { return {}; }
}
import.meta.env.DEVgilt für Vite. Verwenden Sie unter Angular!environment.productionund in Webpack-basierten Projektenprocess.env.NODE_ENV !== 'production'.
Wird PushNotifications.register() bei jedem App-Start aufgerufen, wird das Event registration erneut ausgelöst; so werden Token-Änderungen automatisch an NotiPilot übermittelt.
4. Verwendung
Angular (app.component.ts):
constructor() { initNotiPilot({ city: 'Istanbul' }); }
React (App.tsx):
useEffect(() => { initNotiPilot({ city: 'Istanbul' }); }, []);
Vue (App.vue):
onMounted(() => initNotiPilot({ city: 'Istanbul' }));
Wenn sich der Nutzer anmeldet: await identify(user.id);
Checkliste
-
google-services.jsonhinzugefügt (Android), Push-Capabilities aktiviert (iOS) - Capacitor-Push-Methoden in
AppDelegate.swift(iOS) ergänzt - FCM-V1- und APNs-Zugangsdaten in das Expo-Projekt hochgeladen
- Expo Project ID im Dashboard =
APP_ID, Bundle ID / Package =capacitor.config.ts→appId -
countryund nach Möglichkeitcitywerden als Attributes gesendet
Häufige Probleme
| Symptom | Lösung |
|---|---|
Das Event registration wird nie ausgelöst (iOS) |
Die Methoden in AppDelegate.swift fehlen oder die Capability ist deaktiviert. |
| App stürzt unter Android ab | google-services.json fehlt. |
Token-Konvertierung liefert 4xx |
Prüfen Sie die Werte von projectId und appId (Bundle ID). |
404 unknown_app |
app_id ist im Dashboard nicht registriert. |