Field Log · Entry
조카에게 칭찬스티커 만들어주기 3: EAS projectId와 FCM V1 이해하기
기록 시점: 본문은 2026-08-04에 오프라인 체험판에서 온라인 가족 앱으로 넘어가며 정리한 단계입니다. 2026-09-08에 확인한 프로젝트 README에는 Supabase·FCM을 연결한 가족 파일럿
1.0.0 (4)배포가 기록되어 있습니다. 아래의 미완료 항목은 당시 상태이며, 설치 절차와 실제 가족 기기에서 관찰한 결과를 같은 것으로 보지 않습니다.
용어부터 바로잡기
EAS projectId는 Expo 서버에서 이 앱 프로젝트를 식별하는 UUID다.FCM V1 자격 증명은 Firebase Cloud Messaging으로 Android 알림을 보낼 권한을 증명하는 Google 서비스 계정 키다.- FCM은 Firebase Cloud Messaging의 약자다. EAS 프로젝트 ID와 FCM 서비스 계정은 서로 다른 서비스를 식별한다.
google-services.json과 서비스 계정 private key JSON은 전혀 다른 파일이다. 전자는 앱 등록 정보이고, 후자는 서버 발송 권한이므로 절대 Git에 넣지 않는다.
2편에서 가족 DB와 Webhook까지 연결했다. 이제 Webhook이 만든 푸시 요청을 실제 Android 휴대폰까지 보내는 구간을 연결한다.
1. 알림이 도착하기까지 거치는 네 개의 식별자
푸시 설정이 어려운 이유는 서로 다른 서비스가 비슷한 “프로젝트 ID”와 “키”를 사용하기 때문이다.
| 이름 | 예시 | 누가 사용하나 | 비밀인가 |
|---|---|---|---|
| Android package | com.ruahverce.praisestar | Android·Firebase·EAS가 앱을 식별 | 아니오 |
| EAS projectId | UUID 형식 | Expo가 build·credentials·push token을 한 프로젝트에 귀속 | 아니오 |
| Firebase project ID/number | Firebase가 발급 | FCM sender와 Android 앱 설정을 연결 | 아니오 |
google-services.json | Firebase Android 설정 파일 | 설치된 앱을 해당 Firebase 프로젝트에 등록 | 공식 문서상 비밀 아님 |
| FCM V1 service account key | private key가 든 JSON | EAS/서버가 Google에 알림 발송 권한을 증명 | 비밀 |
칭찬별의 Android package는 이미 app.json에 고정되어 있다.
{
"expo": {
"android": {
"package": "com.ruahverce.praisestar"
}
}
}
Firebase에 Android 앱을 등록할 때 이 값을 대소문자까지 똑같이 입력해야 한다. Firebase는 등록 후 해당 Android 앱의 package name을 바꿀 수 없다고 안내한다.
2. EAS projectId는 무엇인가
EAS는 Expo Application Services의 약자다. Expo 계정 안에 “이 코드 디렉터리는 어떤 Expo 프로젝트인가”를 연결하면 고유 UUID가 만들어진다. 이것이 EAS projectId다.
이 ID는 다음 연결점으로 쓰인다.
- EAS Build 이력과 앱 코드
- Android application identifier와 credentials
- FCM V1 서비스 계정 키
Notifications.getExpoPushTokenAsync({ projectId })가 발급하는 ExpoPushToken
즉 DB 행의 ID도, Android package도, Firebase project ID도 아니다. Expo 쪽 프로젝트 식별자다.
3. Expo 계정과 프로젝트 연결하기
프로젝트 루트에서 로그인 상태를 확인하고 EAS 프로젝트를 만든다.
cd praise-sticker
npx eas-cli login
npx eas-cli whoami
npx eas-cli init
eas init은 새 EAS 프로젝트를 만들거나 기존 프로젝트를 연결한다. 이미 만든 EAS 프로젝트에 연결하려면 공식 CLI의 --id 옵션을 사용할 수 있다.
npx eas-cli init --id YOUR_EAS_PROJECT_ID
npx eas-cli project:info
연결이 끝나면 app.json에 다음과 같은 값이 생긴다.
{
"expo": {
"extra": {
"eas": {
"projectId": "00000000-0000-0000-0000-000000000000"
}
}
}
}
칭찬별은 이 값을 읽어 기기의 ExpoPushToken을 발급한다. projectId가 없으면 앱의 나머지 기능은 동작해도 원격 푸시 토큰 등록을 건너뛴다.
4. Firebase 프로젝트와 Android 앱 만들기
- Firebase Console에서 프로젝트를 만든다.
- 프로젝트 Overview에서 Android 앱 추가를 선택한다.
- Android package name에
com.ruahverce.praisestar를 정확히 입력한다. - 앱을 등록하고
google-services.json을 내려받는다. - 파일을 프로젝트 루트에 둔다.
Expo 설정에도 파일 경로를 추가한다.
{
"expo": {
"android": {
"package": "com.ruahverce.praisestar",
"googleServicesFile": "./google-services.json"
}
}
}
Firebase 공식 문서는 google-services.json이 프로젝트와 앱을 식별하는 비밀이 아닌 설정값을 담는다고 설명한다. 그러나 공개 저장소의 운영 정책에 따라 이 파일도 EAS file variable로 관리할 수 있다.
5. FCM V1 서비스 계정 키 만들기
Firebase Console에서 다음 순서로 이동한다.
- Project settings
- Service accounts
- Generate new private key
- 확인 후 JSON 파일 다운로드
이 파일에는 private key가 들어 있다. 다운로드하기 전에 이름을 정하고 .gitignore에 먼저 등록하는 편이 안전하다.
# Firebase server credentials — never commit
firebase-service-account*.json
서비스 계정 키는 앱 안에 들어가는 파일이 아니다. EAS가 Expo Push Service에서 Google FCM으로 메시지를 전달할 때 사용하는 서버 자격 증명이다.
6. FCM V1 키를 EAS에 업로드하기
npx eas-cli credentials --platform android
대화형 메뉴에서 Android 애플리케이션을 선택한 뒤 다음 항목으로 들어간다.
Google Service Account
→ Manage your Google Service Account Key for Push Notifications (FCM V1)
→ Upload a new service account key
Expo Dashboard를 사용한다면 Project settings → Credentials → Android application identifier → Service Credentials → FCM V1 service account key에서 올릴 수 있다.
업로드 성공 후 로컬 private key JSON은 비밀번호 관리자나 제한된 보관소로 옮긴다. 저장소, 메신저, 앱 .env, Supabase 테이블에는 넣지 않는다.
7. Supabase 공개 환경 변수를 EAS Build에 전달하기
로컬 .env는 Git에서 제외되어 있으므로 원격 EAS Build 서버에 자동으로 존재하지 않는다. preview 환경에 두 개의 공개 클라이언트 값을 등록한다.
npx eas-cli env:create \
--name EXPO_PUBLIC_SUPABASE_URL \
--value https://YOUR_PROJECT_REF.supabase.co \
--environment preview \
--visibility plaintext
npx eas-cli env:create \
--name EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY \
--value YOUR_PUBLISHABLE_KEY \
--environment preview \
--visibility plaintext
npx eas-cli env:list --environment preview
이 값들은 원래 앱 번들에서 읽히는 공개 값이다. EAS에서 secret으로 보이게 설정해도 최종 앱에 포함된 값은 사용자가 읽을 수 있으므로, Supabase RLS가 실제 보안 경계여야 한다.
eas.json의 preview profile에 환경을 명시하면 빌드가 어떤 변수 묶음을 쓰는지 더 분명해진다.
{
"build": {
"preview": {
"distribution": "internal",
"environment": "preview",
"android": {
"buildType": "apk"
}
}
}
}
8. 온라인 preview APK 빌드하기
npx eas-cli build --platform android --profile preview
이 빌드는 기존 오프라인 체험 APK와 다르다.
- Supabase URL과 publishable key가 번들에 들어간다.
- EAS projectId가 들어간다.
google-services.json으로 Android 앱이 Firebase 프로젝트에 등록된다.- EAS에 보관된 FCM V1 서비스 계정 키로 Expo가 Google에 푸시를 보낼 수 있다.
빌드가 끝나면 EAS가 제공하는 설치 링크로 실제 Android 기기에 설치한다. Android 13 이상에서는 앱을 연 뒤 알림 권한도 허용해야 한다.
9. 전체 푸시 경로를 한 줄씩 따라가기
칭찬별에서 스티커 승인 알림은 다음 순서로 이동한다.
1. 보호자가 요청 승인
2. Supabase RPC가 sticker_ledger와 app_notifications를 한 트랜잭션에 기록
3. Database Webhook이 push Edge Function 호출
4. Edge Function이 수신자의 ExpoPushToken 조회
5. Expo Push API에 메시지 전송
6. Expo가 EAS에 저장된 FCM V1 자격 증명으로 Google FCM 호출
7. FCM이 Android 기기에 시스템 알림 전달
EAS projectId는 4번의 ExpoPushToken이 어느 Expo 프로젝트에 속하는지 결정한다. FCM V1 자격 증명은 6번에서 Google에 보낼 권한을 증명한다. 둘 중 하나라도 빠지면 마지막까지 갈 수 없다.
10. 가장 작은 푸시 테스트
- 실제 Android 기기에 온라인 preview APK를 설치한다.
- 앱에서 알림 권한을 허용한다.
- 앱이 ExpoPushToken을 Supabase
push_devices에 등록했는지 확인한다. - Expo의 Push Notifications Tool로 해당 토큰에 테스트 메시지를 보낸다.
- 앱이 열린 상태와 백그라운드 상태를 각각 확인한다.
- 그다음 칭찬별의 실제 승인 흐름으로 Webhook부터 종단 테스트한다.
Expo 도구 테스트는 성공하는데 실제 승인 알림이 오지 않는다면 FCM보다 Supabase Webhook·Edge Function·수신자 토큰 조회를 먼저 본다. 반대로 Expo 도구부터 실패하면 EAS projectId, FCM key, google-services.json, 알림 권한을 먼저 본다.
11. 비슷해 보이는 두 JSON 파일 다시 구분하기
| 파일 | 어디에 쓰나 | Git |
|---|---|---|
google-services.json | Android 앱이 Firebase 프로젝트에 등록되는 client config | 공식상 비밀 아님, 정책에 따라 관리 |
| Firebase service account private key JSON | 서버/EAS가 FCM V1 발송 권한을 얻음 | 절대 커밋 금지 |
이 둘을 바꾸어 넣거나 서로 다른 Firebase 프로젝트에서 받으면 MismatchSenderId, InvalidCredentials 같은 오류가 날 수 있다.
다음 글 네 대의 Android로 가족 파일럿하기에서는 엄마·이모·이모부·아이 실제 기기에서 동기화와 백그라운드 알림을 분리해 검증한다.
참고자료
Reader questions
읽으며 남긴 질문
글을 읽으며 붙잡아 둔 질문입니다. 다시 살펴보거나, 보내지 않은 질문을 한 번에 블로그 주인에게 전달할 수 있습니다.