Daeseon Yoo
Back to project
·Decision·2 min

모바일 리마인더가 진짜 알림으로 — 네이티브 푸시 붙이기

App Store 준비도 감사에서 ‘리마인더 UI는 알림을 약속하지만 모바일엔 아무것도 안 온다(웹 푸시는 RN에 도달 못 함)’가 기능 공백이었다. expo-notifications로 Expo 푸시 토큰을 받아 서버에 등록하고, 스케줄러가 구독을 platform으로 분기(웹=Web Push/VAPID, 네이티브=Expo Push API)해 실제 알림이 가도록 닫았다. root·mobile tsc 모두 통과.

출시 준비도 감사가 짚은 모바일의 ‘지키지 못하는 약속’: 설정 화면의 리마인더는 "정해둔 시각에 알려드려요"라고 하지만, 발송은 전적으로 Web Push(VAPID) 였다. RN 앱엔 웹 푸시 구독이 없으니 모바일 전용 사용자는 알림을 하나도 못 받았다. 이번에 그 공백을 닫았다.

두 갈래의 푸시

핵심은 구독을 platform으로 분기한 것. pushSubscriptions 테이블에 이미 platform 컬럼이 있어, 같은 테이블에 두 종류가 공존한다:

lib/scheduler.tsrunDueReminders()가 발송 시 s.platform === "expo"로 갈라 호출한다. 트리거(영속 호스트의 node-cron / 서버리스의 외부 크론)는 그대로다.

모바일 쪽

웹 구독 입력(pushSubscribeInput)은 endpoint=URL+keys를 강제해 Expo 토큰을 거부한다. 그래서 별도 엔드포인트 POST /api/push/mobile(mobilePushInput = 토큰 문자열, isExpoPushToken로 형식 확인, 토큰 기준 멱등)을 뒀다.

앱(mobile/App.tsx)은 로그인되면 expo-notifications로 권한을 요청하고 getExpoPushTokenAsync({ projectId })로 토큰을 받아 registerPushToken()으로 등록한다(실패는 조용히 무시 — 앱 사용엔 영향 없음). app.jsonexpo-notifications 플러그인 추가, EAS projectId는 이미 연결돼 있다.

검증 / 남은 것

npx tsc --noEmit(root)·npm run typecheck(mobile, expo-notifications 56.0.18 설치 후) 모두 exit 0. 런타임(실기기 알림 수신)은 EAS 빌드 + Apple Developer 계정(APNs 자격증명은 EAS가 빌드 시 생성) 이 있어야 끝까지 확인된다 — 코드는 준비됐고, 그 부분은 계정·빌드 단계.