Geliştiriciler · API v1.0.0
Expo Entegrasyonu
Bu rehber Expo (managed workflow veya development build) ile geliştirilen uygulamalar içindir. Genel API referansı için Genel bakış dosyasına bakın.
Desteklenen sürümler: Expo SDK 50+ (önerilen: güncel SDK) · iOS 13.4+ · Android 7.0+ (API 24)
Uzaktan (remote) push bildirimleri Expo Go içinde çalışmaz (Android'de SDK 53'ten itibaren kaldırıldı). Test için development build veya EAS Build ile üretilmiş bir build kullanın. Push bildirimleri emülatör/simülatörde değil, fiziksel cihazda test edilmelidir.
1. Kurulum
npx expo install expo-notifications expo-device expo-constants expo-secure-store expo-localization expo-crypto
app.json / app.config.js:
{
"expo": {
"plugins": [
["expo-notifications", { "defaultChannel": "default" }]
],
"extra": {
"eas": { "projectId": "EAS-PROJE-ID-NIZ" }
}
}
}
Push kimlik bilgilerini Expo projenize yükleyin (bir kez):
eas credentials # Android: FCM V1 servis hesabı JSON'u, iOS: APNs key
2. NotiPilot istemcisi
src/notipilot.ts:
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';
// NotiPilot panelindeki "Expo Project ID" ile aynı olmalı
const PROJECT_ID: string =
Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId;
const DEVICE_UID_KEY = 'notipilot_device_uid';
// SDK 52 ve öncesinde shouldShowBanner/shouldShowList yerine shouldShowAlert: true kullanın
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: 'Genel',
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>;
/** Uygulama her açıldığında çağırın. */
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, // örn. { city: 'Istanbul' }
},
});
}
/** Kullanıcı giriş yaptığında çağırın. */
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 } : {}),
});
}
/** Kullanıcı çıkış yaptığında çağırın. */
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,
});
}
/** Token yenilendiğinde NotiPilot'u güncel tutar. Uygulama başlangıcında bir kez çağırın. */
export function listenForTokenChanges() {
return Notifications.addPushTokenListener(() => {
registerDevice().catch(() => {});
});
}
3. Kullanım
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();
// Bildirime tıklanınca panelden gönderilen `data` alanını okuyun
const tapSub = Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data;
// örn. data.screen === 'product' → navigation.navigate('Product', { id: data.product_id })
});
return () => {
tokenSub.remove();
tapSub.remove();
};
}, []);
// Giriş sonrası: await identify(user.id, { gender: user.gender });
return null;
}
4. Kontrol listesi
- Paneldeki Expo Project ID =
extra.eas.projectId -
eas credentialsile FCM V1 ve APNs kimlik bilgileri yüklendi - Fiziksel cihazda, development/production build ile test edildi
-
countryve mümkünsecitygönderiliyor (konum segmentleri için) - Giriş sonrası
identify, çıkıştalogoutçağrılıyor
Sık karşılaşılan sorunlar
| Belirti | Çözüm |
|---|---|
404 unknown_app |
app_id panelde kayıtlı değil veya yanlış. Panelde Expo Project ID'yi kontrol edin. |
| Token alınamıyor | Fiziksel cihaz kullanın, bildirim iznini kontrol edin, projectId parametresini verdiğinizden emin olun. |
| Kayıt başarılı ama bildirim gelmiyor | Expo projesinde FCM V1 / APNs kimlik bilgileri eksik olabilir. Expo push aracı ile token'a test bildirimi gönderin. |
| Android'de bildirim sessiz geliyor | default ID'li bir bildirim kanalını HIGH önem seviyesiyle oluşturun. |