Geliştiriciler · API v1.0.0
Ionic (Capacitor) Entegrasyonu
Bu rehber Ionic + Capacitor ile geliştirilen Android ve iOS uygulamaları içindir (Angular, React veya Vue fark etmez). Genel API referansı için Genel bakış.
Desteklenen sürümler
| Bileşen | Minimum | Önerilen |
|---|---|---|
| Capacitor | 6 | güncel |
@capacitor/push-notifications |
6.x | güncel |
| Ionic Framework | 7 | güncel |
| Android | 6.0 (API 23) | targetSdk 35+ |
| iOS | 13.0 | 15.0+ |
Cordova tabanlı eski Ionic projeleri için resmi destek yoktur; Capacitor'a geçiş önerilir. PWA/web push 1.0.0'da desteklenmez.
Nasıl çalışır?
NotiPilot 1.0.0 bildirimleri Expo Push Service üzerinden teslim eder. Uygulamanız:
@capacitor/push-notificationsile native token'ı alır (Android: FCM token, iOS: APNs token),- Bu token'ı Expo token servisinde bir Expo Push Token'a dönüştürür,
- Expo Push Token'ı NotiPilot'a
register-deviceile kaydeder.
1. Ön hazırlık (bir kez)
- Android: Firebase konsolunda Android uygulamanızı ekleyin,
google-services.jsondosyasınıandroid/app/içine koyun. - iOS: Apple Developer'da Push Notifications capability'sini açın ve bir APNs
.p8anahtarı oluşturun (adımlar: iOS rehberi). - expo.dev üzerinde bir proje oluşturun, proje ID'sini not edin ve Credentials bölümüne FCM V1 servis hesabı JSON'unu (Android) ve APNs
.p8anahtarını (iOS) yükleyin. - NotiPilot panelinde uygulamanızı ekleyin: Expo Project ID, Expo Access Token, Android Package, iOS Bundle ID (=
capacitor.config.tsiçindekiappId).
2. Kurulum
npm install @capacitor/push-notifications @capacitor/preferences @capacitor/app @capacitor/device
npx cap sync
iOS: Xcode'da (npx cap open ios) App target → Signing & Capabilities → Push Notifications ve Background Modes → Remote notifications ekleyin. ios/App/App/AppDelegate.swift içine şunları ekleyin:
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: android/variables.gradle içinde minSdkVersion = 23 olmalıdır.
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 = 'EXPO-PROJE-ID-NIZ'; // Paneldeki "Expo Project ID"
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 build'leri APNs sandbox kullanır
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;
}
/** Uygulama açılışında çağırın. */
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, // örn. { city: 'Istanbul' }
},
});
} catch (e) {
console.warn('[NotiPilot] register failed', e);
}
});
await PushNotifications.addListener('registrationError', (err) =>
console.warn('[NotiPilot] native registration error', err),
);
// Bildirime tıklandı — panelden gönderilen özel veri
await PushNotifications.addListener('pushNotificationActionPerformed', (action: ActionPerformed) => {
const raw = action.notification.data?.body;
const data = typeof raw === 'string' ? safeJson(raw) : raw ?? action.notification.data;
// örn. data.screen === 'product' → router.navigate(...)
});
if (Capacitor.getPlatform() === 'android') {
await PushNotifications.createChannel({ id: 'default', name: 'Genel', importance: 5 });
}
let perm = await PushNotifications.checkPermissions();
if (perm.receive === 'prompt') perm = await PushNotifications.requestPermissions();
if (perm.receive === 'granted') await PushNotifications.register();
}
/** Kullanıcı giriş yaptığında çağırın. */
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.DEVVite içindir. Angular kullanıyorsanız!environment.production, Webpack tabanlı projelerdeprocess.env.NODE_ENV !== 'production'kullanın.
PushNotifications.register() her uygulama açılışında çağrıldığında registration olayı yeniden tetiklenir; bu sayede token değişiklikleri otomatik olarak NotiPilot'a iletilir.
4. Kullanım
Angular (app.component.ts):
constructor() { initNotiPilot({ city: 'Istanbul' }); }
React (App.tsx):
useEffect(() => { initNotiPilot({ city: 'Istanbul' }); }, []);
Vue (App.vue):
onMounted(() => initNotiPilot({ city: 'Istanbul' }));
Kullanıcı giriş yaptığında: await identify(user.id);
Kontrol listesi
-
google-services.jsoneklendi (Android), Push capability'leri açık (iOS) - iOS
AppDelegate.swiftiçine Capacitor push metotları eklendi - FCM V1 ve APNs kimlik bilgileri Expo projesine yüklendi
- Paneldeki Expo Project ID =
APP_ID, Bundle ID / Package =capacitor.config.ts→appId -
countryve mümkünsecityattributes olarak gönderiliyor
Sık karşılaşılan sorunlar
| Belirti | Çözüm |
|---|---|
registration olayı hiç gelmiyor (iOS) |
AppDelegate.swift metotları eksik veya capability kapalı. |
| Android'de uygulama çöküyor | google-services.json eksik. |
Token dönüşümü 4xx dönüyor |
projectId ve appId (bundle ID) değerlerini kontrol edin. |
404 unknown_app |
app_id panelde kayıtlı değil. |