expo-notifications เวอร์ชัน 0.29+ รองรับ FCM HTTP v1 (ที่ Google บังคับใช้ตั้งแต่มิถุนายน 2024) โดยไม่ต้องตั้งค่า Legacy Server Key อีกต่อไป
Expo Push Service ใช้งานฟรีไม่จำกัดจำนวนข้อความ แต่ส่งได้สูงสุด 100 tokens ต่อ HTTP request และมี rate limit 600 ครั้งต่อวินาที
iOS ต้องอัปโหลด APNs Auth Key (.p8) เข้าสู่ EAS หรือ Expo Dashboard ส่วน Android ต้องส่ง google-services.json และเปิดใช้ FCM API ใน Google Cloud Console
การจัดการ Notification ขณะแอปปิดต้องใช้ Notifications.getLastNotificationResponseAsync() ร่วมกับ Deep Link ของ Expo Router เพื่อพาผู้ใช้ไปยังหน้าจอที่ถูกต้อง
Background notifications บน Android ต้องตั้งค่า Notification Channel อย่างน้อย 1 ช่อง ส่วน iOS ต้องเปิด content-available: true ใน payload
ทดสอบบนอุปกรณ์จริงเท่านั้น Push Notifications ไม่ทำงานบน iOS Simulator (ยกเว้น Xcode 14+ ที่รองรับการลากไฟล์ .apns)
ในหน้านี้
ตั้งค่า expo-notifications ในโปรเจกต์ React Native
ตั้งค่า FCM HTTP v1 สำหรับ Android
ตั้งค่า APNs Auth Key สำหรับ iOS
ขอ Expo Push Token และเก็บไว้ที่เซิร์ฟเวอร์
ส่งการแจ้งเตือนผ่าน Expo Push API
จัดการ Notification ใน Foreground และ Background
Deep Link จาก Notification ไปยังหน้าจอเฉพาะ
Expo Push Service ฟรีหรือไม่ และเมื่อไหร่ควรใช้ FCM/APNs โดยตรง
ปัญหาที่พบบ่อยและวิธีแก้
คำถามที่พบบ่อย
ตั้งค่า expo-notifications ในโปรเจกต์ React Native
เริ่มจากการติดตั้ง expo-notifications, expo-device และ expo-constants ซึ่งจำเป็นทั้งสามตัวสำหรับการขอ Push Token อย่างถูกต้อง สำหรับโปรเจกต์ที่ใช้ Expo SDK 52 หรือใหม่กว่า ให้รันคำสั่ง npx expo install เพื่อให้ระบบเลือกเวอร์ชันที่เข้ากันได้กับ SDK ของคุณโดยอัตโนมัติ (เคยพลาดใช้ npm install ตรงๆ แล้วเจอเวอร์ชันชนกัน เสียเวลาไปครึ่งเช้าเลย)
npx expo install expo-notifications expo-device expo-constants
จากนั้นเพิ่ม config plugin ลงใน app.json หรือ app.config.ts เพื่อให้ EAS Build สร้างไฟล์ native ที่จำเป็น และระบุไอคอนสำหรับแสดงในแถบสถานะของ Android (iOS จะใช้ App Icon โดยอัตโนมัติ) ไอคอนต้องเป็น PNG พื้นหลังโปร่งใส ขนาด 96×96 px ขึ้นไป สีเดียวเท่านั้น ไม่งั้น Android จะแสดงเป็นกล่องสีขาวเปล่าๆ ดูแย่มาก
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#1E90FF",
"defaultChannel": "default",
"sounds": ["./assets/notification-sound.wav"]
}
]
],
"android": {
"googleServicesFile": "./google-services.json",
"package": "com.example.myapp"
},
"ios": {
"bundleIdentifier": "com.example.myapp"
}
}
}
หลังแก้ app.json เสร็จ ต้องสร้าง Development Build ใหม่ด้วย eas build --profile development --platform all เพราะ Expo Go ตั้งแต่ SDK 53 เป็นต้นไปไม่รองรับการรับ Remote Push Notifications อีกต่อไป ใช้ได้เฉพาะ Local Notifications เท่านั้น จุดนี้คนพลาดกันเยอะ
ตั้งค่า FCM HTTP v1 สำหรับ Android
ตั้งแต่วันที่ 20 มิถุนายน 2024 Google ได้ปิด FCM Legacy HTTP API อย่างถาวร ผู้พัฒนาทุกคนต้องย้ายไปใช้ FCM HTTP v1 API ซึ่งใช้ Service Account ของ Google Cloud แทน Server Key แบบเดิม Expo Notifications เวอร์ชัน 0.29 ขึ้นไปจัดการส่วนนี้ให้ทั้งหมด แต่คุณต้องเตรียมไฟล์ credentials ให้ถูกต้องก่อน
ขั้นตอนการตั้งค่าจะเป็นแบบนี้:
เข้า Firebase Console สร้างโปรเจกต์ใหม่หรือเลือกโปรเจกต์ที่มีอยู่ จากนั้นเพิ่ม Android App โดยใช้ Package Name เดียวกับใน app.json
ดาวน์โหลดไฟล์ google-services.json มาวางไว้ที่ root ของโปรเจกต์
ไปที่ Project Settings → Service accounts → Generate new private key เพื่อดาวน์โหลดไฟล์ JSON ของ Service Account
อัปโหลดไฟล์ Service Account ขึ้น EAS ด้วยคำสั่ง eas credentials เลือก Android → Push Notifications → Upload Service Account Key
ตรวจสอบใน Google Cloud Console ว่าได้เปิดใช้งาน "Firebase Cloud Messaging API (V1)" แล้ว เพราะบางโปรเจกต์ใหม่จะปิดไว้เป็นค่าเริ่มต้น
เพื่อทดสอบว่าการตั้งค่าถูกต้อง ให้รันคำสั่ง eas credentials แล้วเลือก Android คุณจะเห็นสถานะ "FCM V1 service account: Configured" หากยังขึ้นเป็น Legacy Server Key ให้ลบทิ้งและอัปโหลดใหม่ ตรงนี้ผมเจอมาเองตอนย้ายโปรเจกต์เก่า ลืมลบ Server Key เก่าทำให้ Expo ส่งไปทาง legacy ก่อนแล้วล้ม
ตั้งค่า APNs Auth Key สำหรับ iOS
สำหรับ iOS ผมแนะนำให้ใช้ APNs Auth Key (.p8) แทน APNs Certificate (.p12) เพราะ Auth Key ไม่หมดอายุและใช้กับทุกแอปใน Apple Developer Account เดียวกันได้ ขั้นตอนคือเข้า Apple Developer Portal → Keys สร้าง Key ใหม่ ติ๊กช่อง Apple Push Notifications service (APNs) แล้วดาวน์โหลดไฟล์ .p8 (ดาวน์โหลดได้ครั้งเดียวเท่านั้น เก็บใส่ password manager ไว้เลยจะดีกว่า)
จากนั้นรัน eas credentials เลือก iOS → Push Notifications → Set up Push Notifications แล้วใส่:
ไฟล์ .p8 ที่เพิ่งดาวน์โหลด
Key ID (10 ตัวอักษร แสดงในหน้า Key ที่สร้าง)
Team ID (พบใน Membership ของ Apple Developer Portal)
EAS จะอัปโหลด Push Key ไปยังเซิร์ฟเวอร์ของ Expo และใช้ส่งการแจ้งเตือนผ่าน APNs ให้อัตโนมัติ ไม่ต้องเขียนโค้ดสื่อสารกับ APNs โดยตรง สำหรับโปรเจกต์ที่ใช้ Bare Workflow ให้ตรวจว่าได้เปิด Capability "Push Notifications" ใน Xcode → Signing & Capabilities ของ Target ทั้ง App และ Notification Service Extension (ถ้ามี) ค่าใช้จ่ายของ Apple Developer Program ($99/ปี) ครอบคลุมการใช้ APNs ไม่จำกัดอยู่แล้ว ไม่ต้องเสียเพิ่ม
ขอ Expo Push Token และเก็บไว้ที่เซิร์ฟเวอร์
หัวใจของระบบ Push Notifications คือ Token ที่ผูกกับอุปกรณ์ผู้ใช้ โค้ดด้านล่างเป็นโมดูล usePushToken.ts ที่ขอสิทธิ์ ขอ Token และส่งไปเก็บที่ backend ทำงานได้ทั้ง iOS และ Android โดยจัดการ Edge Case ครบ เช่น อุปกรณ์ Simulator, การปฏิเสธสิทธิ์ และการตั้งค่า Notification Channel บน Android
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
import { useEffect, useState } from 'react';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export async function registerForPushNotificationsAsync(): Promise<string | null> {
if (!Device.isDevice) {
console.warn('Push Notifications ต้องใช้บนอุปกรณ์จริง');
return null;
}
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'การแจ้งเตือนทั่วไป',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#1E90FF',
});
}
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
console.warn('ผู้ใช้ปฏิเสธสิทธิ์ Push Notifications');
return null;
}
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ??
Constants.easConfig?.projectId;
if (!projectId) {
throw new Error('ไม่พบ EAS Project ID ตรวจสอบ app.json');
}
const tokenData = await Notifications.getExpoPushTokenAsync({ projectId });
return tokenData.data; // รูปแบบ ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
}
export function usePushToken(userId: string) {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
registerForPushNotificationsAsync().then(async (pushToken) => {
if (!pushToken) return;
setToken(pushToken);
await fetch('https://api.example.com/devices', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, pushToken, platform: Platform.OS }),
});
});
}, [userId]);
return token;
}
สิ่งสำคัญคือ ต้องเก็บ Token พร้อม User ID ที่ฐานข้อมูลของคุณ เพราะอุปกรณ์เครื่องเดียวอาจมีหลายผู้ใช้ และ Token อาจเปลี่ยนเมื่อผู้ใช้ติดตั้งแอปใหม่ ลบข้อมูลแอป หรือเปลี่ยนเครื่อง คุณควรเรียก endpoint ลบ Token เมื่อผู้ใช้ Logout ด้วย เพื่อไม่ให้ผู้ใช้คนใหม่ได้รับการแจ้งเตือนของคนเก่า (ผมเคยพลาดตรงนี้แล้ว user complain ยกใหญ่) บทความ คู่มือ Zustand สำหรับ React Native มีตัวอย่างการจัดเก็บ Token พร้อม persist ลง MMKV ที่ใช้ร่วมกันได้ และถ้าจะเก็บ Token ในเครื่องด้วยให้อ่าน คู่มือจัดเก็บข้อมูล React Native ประกอบจะเห็นภาพรวมชัดเจน
หมายเหตุ: เริ่ม Expo SDK 52 API shouldShowAlert ถูก deprecated แล้ว ให้เปลี่ยนเป็น shouldShowBanner และ shouldShowList แทน เพื่อรองรับการแสดงผลแบบใหม่ของ iOS 17+
ส่งการแจ้งเตือนผ่าน Expo Push API
การส่ง Push Notification ผ่าน Expo Push Service ทำได้ด้วย HTTP POST ธรรมดาไปที่ https://exp.host/--/api/v2/push/send ไม่ต้องใช้ Authentication ใดๆ (แต่แนะนำให้ตั้ง Access Token ใน Expo Dashboard เพื่อเพิ่มความปลอดภัย) ส่งได้สูงสุด 100 Token ต่อ Request และ Expo Push Service จะกระจายไปยัง FCM/APNs ให้อัตโนมัติ สบายมาก
// server/sendPush.ts (Node.js / Bun)
type PushMessage = {
to: string | string[];
title: string;
body: string;
data?: Record<string, unknown>;
sound?: 'default' | null;
badge?: number;
channelId?: string; // Android เท่านั้น
priority?: 'default' | 'normal' | 'high';
};
export async function sendPush(messages: PushMessage[]) {
const chunks: PushMessage[][] = [];
for (let i = 0; i < messages.length; i += 100) {
chunks.push(messages.slice(i, i + 100));
}
const results = [];
for (const chunk of chunks) {
const res = await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EXPO_ACCESS_TOKEN}`,
},
body: JSON.stringify(chunk),
});
const json = await res.json();
results.push(...json.data);
}
// ตรวจสอบ Token ที่หมดอายุ
const invalidTokens = results
.filter((r) => r.status === 'error' && r.details?.error === 'DeviceNotRegistered')
.map((r) => r.message);
if (invalidTokens.length > 0) {
await removeTokensFromDb(invalidTokens);
}
return results;
}
หลังส่งแล้ว Expo จะตอบกลับ Receipt ID ของแต่ละข้อความ คุณควรเรียก https://exp.host/--/api/v2/push/getReceipts หลังจากนั้นสักประมาณ 15 นาที เพื่อตรวจว่ามี Token ใดถูก FCM/APNs ปฏิเสธ (DeviceNotRegistered, MessageRateExceeded) แล้วลบออกจากฐานข้อมูล ไม่งั้น Token เสียจะสะสมและทำให้ Expo จำกัด Rate ของบัญชีคุณในระยะยาว อ่าน เอกสาร Expo Push Notifications เพื่อดูตัวอย่าง payload และตัวเลือกเพิ่มเติม เช่น mutableContent, categoryId, subtitle
จัดการ Notification ใน Foreground และ Background
Notification ใน React Native แบ่งเป็น 3 สถานะหลัก และแต่ละสถานะใช้ Listener ต่างกัน ตารางนี้สรุปสิ่งที่คุณต้องจำให้แม่นยำเพื่อไม่ให้พลาดข้อความใดๆ:
สถานะแอป Listener ที่ใช้ ทำงานเมื่อไหร่
Foreground (เปิดอยู่) addNotificationReceivedListenerทันทีที่ Notification มาถึงและแสดงผล
Background (พักหน้าจอ) addNotificationResponseReceivedListenerเมื่อผู้ใช้แตะ Notification ที่แสดงในแถบ
Killed (ปิดแอป) getLastNotificationResponseAsyncครั้งเดียวตอนแอปเปิดจาก Notification
Silent (ข้อมูลล้วน) registerTaskAsync (TaskManager)ทำงานเบื้องหลังไม่แสดงผล
import { useEffect, useRef } from 'react';
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';
export function useNotificationListeners() {
const receivedRef = useRef<Notifications.Subscription>();
const responseRef = useRef<Notifications.Subscription>();
useEffect(() => {
// 1. Foreground: แอปเปิดอยู่และได้รับ notification
receivedRef.current = Notifications.addNotificationReceivedListener(
(notification) => {
console.log('ได้รับ:', notification.request.content);
},
);
// 2. Background → Foreground: ผู้ใช้แตะ notification
responseRef.current = Notifications.addNotificationResponseReceivedListener(
(response) => {
const { screen, params } = response.notification.request.content.data;
if (screen) router.push({ pathname: screen, params });
},
);
// 3. Killed → Open: แอปเปิดจาก notification ที่แตะตอนปิด
Notifications.getLastNotificationResponseAsync().then((response) => {
if (!response) return;
const { screen, params } = response.notification.request.content.data;
if (screen) router.push({ pathname: screen, params });
});
return () => {
receivedRef.current?.remove();
responseRef.current?.remove();
};
}, []);
}
เรียก useNotificationListeners() ใน Root Layout ของ Expo Router และ React Navigation v7 เพียงครั้งเดียว ระบบจะดักทุก Notification ที่เข้ามา ไม่ว่าแอปจะอยู่ในสถานะใด สำหรับการประมวลผล Silent Push (ข้อความไม่แสดงผลที่ใช้ Sync ข้อมูล) ใช้ TaskManager.defineTask ร่วมกับ Notifications.registerTaskAsync เพื่อให้โค้ดทำงานเบื้องหลังแม้แอปจะถูก Suspend อยู่
Deep Link จาก Notification ไปยังหน้าจอเฉพาะ
วิธีที่ดีที่สุดในการพาผู้ใช้ไปยังหน้าจอเฉพาะคือใส่ข้อมูล route ลงในฟิลด์ data ของ Notification Payload ตัวอย่างเช่น เมื่อมีคนคอมเมนต์โพสต์ของผู้ใช้ ให้ส่ง:
{
"to": "ExponentPushToken[xxx]",
"title": "มีคอมเมนต์ใหม่",
"body": "Somchai แสดงความเห็นบนโพสต์ของคุณ",
"data": {
"screen": "/posts/[id]",
"params": { "id": "42", "commentId": "987" }
},
"channelId": "comments",
"sound": "default"
}
ที่ฝั่งแอป ตัว Listener ในส่วนก่อนหน้าจะเรียก router.push({ pathname: '/posts/[id]', params: { id: '42', commentId: '987' } }) ทันทีที่ผู้ใช้แตะ Notification ทั้งจากสถานะ Background และ Killed ระบบ Typed Routes ของ Expo Router v4 จะช่วยตรวจ pathname ให้คุณตอนคอมไพล์ ลดโอกาสเขียน path ผิด (ผมชอบฟีเจอร์นี้มาก เพราะตอนเปลี่ยนชื่อ route แล้วลืมแก้ใน Push Payload เคยทำให้บั๊กหลุดถึง production)
หากต้องการให้ Notification ทำงานข้ามแอป (เช่น เปิดเว็บไซต์หรือแอปอื่น) ใส่ Universal Link (iOS) หรือ App Link (Android) ในฟิลด์ data แล้วใช้ Linking.openURL() ใน Listener แทน เทคนิคนี้ดีกว่าการใช้ Custom Scheme เพราะ Browser ของระบบเปิด Universal Link ได้โดยตรงเมื่อแอปไม่ได้ติดตั้ง ลด Friction ของ Onboarding ลงอย่างมาก
Expo Push Service ฟรีหรือไม่ และเมื่อไหร่ควรใช้ FCM/APNs โดยตรง
Expo Push Service ฟรีไม่จำกัดจำนวนข้อความ แม้แอปของคุณจะ Eject ออกจาก Expo ก็ยังใช้ได้ ข้อจำกัดที่ Expo ระบุไว้คือ 600 requests ต่อวินาทีต่อบัญชี และ 100 tokens ต่อ HTTP request หากต้องการมากกว่านี้ต้องติดต่อทีม Expo เพื่อขอ Rate Limit สูงขึ้น
แล้วเมื่อไหร่ควรเลิกใช้ Expo Push และยิง FCM/APNs ตรงๆ? พิจารณาเคสเหล่านี้:
ต้องการ Latency ต่ำกว่า 2 วินาที: Expo Push เพิ่ม Hop หนึ่งช่วงระหว่างเซิร์ฟเวอร์คุณกับ FCM/APNs
ต้องการฟีเจอร์เฉพาะของ FCM/APNs เช่น Topic Messaging, Conditional Targeting, Notification Group Summary บน iOS
ส่งวันละหลายล้านข้อความ: Rate Limit ของ Expo Push อาจไม่พอ
ต้องการการรายงาน Delivery Receipt แบบเรียลไทม์: Expo Receipts ต้องดึงเป็น Batch
หากเลือกยิงตรง ให้ใช้ Firebase Admin SDK สำหรับ FCM และ node-apn หรือ @parse/node-apn สำหรับ APNs โดยต้องเก็บ Native Device Token (ไม่ใช่ Expo Push Token) ซึ่งดึงได้จาก Notifications.getDevicePushTokenAsync() ข้อเสียคือต้องดูแล Service Account, Auth Key, และ Retry Logic เองทั้งหมด ส่วนใหญ่ทีมขนาดเล็กถึงกลางใช้ Expo Push Service เพียงพอเพราะลดความซับซ้อนได้มาก (ทีมผมเองตอนยังต่ำกว่า 50k MAU ก็ใช้ Expo ตลอด ไม่มีปัญหา)
ปัญหาที่พบบ่อยและวิธีแก้
ผู้พัฒนาส่วนใหญ่ติดปัญหาเดิมๆ ที่ Expo Push Service ส่งสำเร็จแต่ผู้ใช้ไม่ได้รับการแจ้งเตือน ลองตรวจสอบตามลำดับนี้:
Android ไม่ได้รับเลย: ตรวจว่า google-services.json ตรงกับ Package Name ใน app.json และ Project ID ใน Firebase Console เปิดใช้งาน FCM API V1 แล้ว
iOS ได้รับเฉพาะตอนแอปเปิด: ตรวจ Capability "Push Notifications" และ "Background Modes → Remote notifications" ใน Xcode
ไอคอนเป็นกล่องสีขาวบน Android: ไอคอน Notification ต้องเป็น PNG พื้นหลังโปร่งใส สีเดียว ขนาดอย่างน้อย 96×96 px ใช้ Asset Studio ใน Android Studio สร้างได้
Notification ไม่ส่งเสียง: ใส่ "sound": "default" ใน payload และตั้ง importance: AndroidImportance.MAX ที่ Channel
Token เปลี่ยนทุกครั้งที่เปิดแอป: ปกติครับ ให้เก็บลงฐานข้อมูลทุกครั้งและทำ Upsert โดยใช้ userId + platform เป็น Key
DeviceNotRegistered ต่อเนื่อง: ผู้ใช้ปิด Notification จากระดับ OS หรือถอนการติดตั้งแอป ให้ลบ Token ออกจาก DB
หากต้องการ Log ละเอียดบน iOS เปิด Console.app เลือกอุปกรณ์ กรองด้วยคำว่า apsd จะเห็น Log ของระบบ APNs ทุกครั้งที่ Push เข้ามา ส่วน Android ใช้ adb logcat | grep -i fcm เพื่อดู Log ของ Firebase อย่าลืมตรวจสอบ Battery Optimization บนเครื่อง Xiaomi, OPPO, Huawei เพราะระบบเหล่านี้ปิด Background Service ของแอปที่ไม่ได้ใช้บ่อย และทำให้ Push หายไป (ลูกค้าผมเคยร้องเรียนเรื่องนี้บ่อยจนต้องทำหน้า in-app บอกวิธีปิด battery saver โดยเฉพาะ)
หมายเหตุ: ตั้งแต่ Android 13 (API 33) ระบบบังคับให้ขอ Permission POST_NOTIFICATIONS ที่ Runtime expo-notifications v0.27+ จัดการให้อัตโนมัติเมื่อเรียก requestPermissionsAsync() แต่อย่าลืมว่าผู้ใช้สามารถปฏิเสธได้และคุณต้องมี Fallback UI
คำถามที่พบบ่อย
Push Notifications ใน Expo Go ใช้งานได้ไหม?
ไม่ได้ตั้งแต่ Expo SDK 53 เป็นต้นไป Expo Go รองรับเฉพาะ Local Notifications เท่านั้น หากต้องการทดสอบ Remote Push Notifications ต้องสร้าง Development Build ด้วย eas build --profile development ก่อน
ใช้ Firebase ส่ง Notification ไปแอป iOS ได้ไหม?
ได้ครับ FCM ทำหน้าที่เป็น Proxy ส่งต่อไปยัง APNs โดยอัตโนมัติเมื่อคุณอัปโหลด APNs Auth Key ขึ้น Firebase Console แต่ถ้าใช้ Expo Push Service อยู่แล้ว ไม่ต้องตั้งค่า Firebase ฝั่ง iOS เพราะ Expo จะส่งตรงผ่าน APNs
ทำไม Push Notification ไม่ทำงานบน iOS Simulator?
เพราะ APNs ออกแบบมาสำหรับอุปกรณ์จริงเท่านั้น Simulator ไม่มี Device Token จริงให้ใช้ ทางเลือกคือใช้ Xcode 14+ ที่รองรับการลากไฟล์ .apns เข้า Simulator เพื่อจำลอง Notification แต่จะไม่ผ่าน APNs Server จริง
เก็บ Expo Push Token ที่ฝั่ง client ปลอดภัยไหม?
Token ตัวเองไม่เป็นความลับสูง แต่ใครก็ตามที่มี Token สามารถส่งข้อความหา Device นั้นได้ ดังนั้นเก็บที่ Backend ของคุณเท่านั้น และอย่าฝังลงใน Client Bundle ใช้ HTTPS เสมอ และตั้ง Expo Access Token ใน Dashboard เพื่อจำกัดว่าใครส่ง Push ได้
Expo Push Token ต่างจาก FCM/APNs Token อย่างไร?
Expo Push Token เป็น Wrapper ที่ Expo Push Service ใช้ map ไปยัง Native Device Token ของ FCM/APNs รูปแบบคือ ExponentPushToken[xxx] ส่วน Native Token เป็นสตริง hex ยาวๆ ใช้กับ FCM/APNs โดยตรง ดึงได้ด้วย getDevicePushTokenAsync()