CloudStock / Web Push 알림
제목: CloudStock/Web Push 알림
상위 문서: [[CloudStock]] CloudStock Phase 3.5 의 구현·운영 메모. 미국 시세 cron 이 끝났을 때 사용자 기기로 Web Push 알림을 보내기 위한 모든 부품을 정리한다. ## 개요 - 트리거: `fetchUSMarket` workflow 의 마지막 단계가 성공으로 완료될 때 - 수신: 사용자가 admin 페이지에서 직접 알림을 켠 기기 전체 (브라우저별 endpoint 단위) - 운영 UI: admin 페이지에서 기기별 등록 / 해제, 그리고 임의 테스트 발송 가능 - 라이브러리 의존성 없음 — Cloudflare Workers 위에서 WebCrypto 만 사용해 직접 구현 ## 사양 다음 RFC 들을 직접 구현한다. - RFC 8030: Generic Event Delivery Using HTTP Push - RFC 8188: Encrypted Content-Encoding for HTTP (aes128gcm) - RFC 8291: Message Encryption for Web Push - RFC 8292: VAPID (Voluntary Application Server Identification) for Web Push VAPID JWT 는 ES256 으로 직접 서명하고, 페이로드는 ECDH(P-256) + HKDF + AES-128-GCM 으로 암호화해서 `Content-Encoding: aes128gcm` 으로 PUT 한다. ## 코드 구조 | 위치 | 역할 | | --- | --- | | `public/sw.js` | Service Worker. `push` 이벤트 수신 → `showNotification`, `notificationclick` 처리 | | `src/scripts/push.ts` | 브라우저 측 구독/해제, 권한 처리, 서버 등록 호출 | | `worker/push/webpush.ts` | VAPID JWT 서명, aes128gcm 페이로드 암호화, 푸시 서비스로 PUT 전송 | | `worker/push/notify.ts` | 등록된 모든 구독을 fan-out, 실패 endpoint 정리 | | `worker/routes/push.ts` | `POST /api/push/subscribe`, `DELETE /api/push/subscribe`, `POST /api/push/test` 등 | | D1 migration `0005` | `push_subscriptions` 테이블 생성 | `fetchUSMarket` 의 마지막 step 에서 `notify.ts` 의 fan-out 을 호출한다. AI 단계와 마찬가지로 try/catch 로 격리해 푸시 실패가 시세 저장을 막지 않도록 한다. ## D1 스키마 (migration 0005) `push_subscriptions` 테이블은 endpoint 를 PK 로 하고, p256dh / auth 키와 user agent, 생성 시각을 함께 보관한다. 410 Gone / 404 Not Found 응답을 받은 endpoint 는 fan-out 루프에서 즉시 삭제한다. ## VAPID 키 운영 - 공개키: `wrangler.toml` 의 `[vars]` 섹션에 `VAPID_PUBLIC_KEY` 로 노출 (프론트가 PushManager.subscribe 의 `applicationServerKey` 로 사용) - 비밀키: `wrangler secret put VAPID_PRIVATE_KEY` 로 입력 (Worker 만 접근, 절대 git 에 커밋 금지) - `VAPID_SUBJECT` 도 vars 로 노출 — 보통 `mailto:` 형식의 운영자 연락처 키 페어는 P-256 으로 생성하고, base64url (no padding) 인코딩으로 저장한다. ## 운영 셋업 절차 1. VAPID 키 페어 생성 (P-256, base64url 인코딩) 2. `wrangler.toml` `[vars]` 에 `VAPID_PUBLIC_KEY`, `VAPID_SUBJECT` 추가 3. `wrangler secret put VAPID_PRIVATE_KEY` 로 비밀키 입력 4. D1 마이그레이션 apply - 로컬: `wrangler d1 migrations apply <DB> --local` - 원격: `wrangler d1 migrations apply <DB> --remote` 5. `wrangler deploy` 후 admin 페이지 접속 → 알림 권한 허용 → "이 기기 등록" 클릭 6. admin 페이지의 "테스트 발송" 버튼으로 즉시 도착 여부 확인 7. 다음 cron 사이클에서 자동 발송 확인 ## 트러블슈팅 - iOS Safari 는 홈 화면 설치된 PWA 에서만 Web Push 가 동작 — 일반 탭에서는 권한 자체가 나오지 않는다 - 401/403: VAPID JWT 의 `aud` 가 endpoint origin 과 일치하는지, `exp` 가 24시간 이내인지 확인 - 404/410: 사용자가 권한을 끄거나 브라우저에서 사이트 데이터를 지운 경우 — fan-out 루프가 자동 정리 ## 관련 문서 - 상위: [[CloudStock]] (Phase 3.5 항목) - 환경변수 / 바인딩 목록은 [[CloudStock]] 부록 A, B 참고