Skip to content

feat: 앱 아이콘 뱃지 동기화 및 알림 실시간 갱신 구현 - #280

Merged
kanghaeun merged 12 commits into
devfrom
feat/279-silent-push-badge-update
Jun 26, 2026
Merged

feat: 앱 아이콘 뱃지 동기화 및 알림 실시간 갱신 구현#280
kanghaeun merged 12 commits into
devfrom
feat/279-silent-push-badge-update

Conversation

@kanghaeun

@kanghaeun kanghaeun commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

작업 요약

  • 알림 읽음 처리 후 앱 뱃지가 줄어들지 않던 문제 수정 — WebBridge 즉각 반영 및 Silent Push 전 기기 동기화
  • 새 알림 도착 시 뱃지 증가 및 알림 목록 실시간 갱신 (SSE 기반)
  • 앱 WebView에서 SSE가 동작하지 않던 버그 수정

배경

서버는 읽음 처리 API 응답에 갱신된 unreadCount를 내려주고 silent push도 발송하고 있었으나, FE에서 OS 레벨 뱃지 업데이트를 하지 않아 뱃지 숫자가 줄어들지 않는 문제가 있었음

백엔드에서 SSE 이벤트 tournament-item-parsedsilent-sync (type 분기) 변경 및UNREAD_COUNT_CHANGED silent-sync 이벤트가 추가됨에 따라 클라이언트 대응

동작 흐름

뱃지 DOWN (읽음 처리)

즉각 반영

알림 클릭
  → read API 호출
  → unreadCount 응답
  → WebBridge(WEB_REQ_SET_BADGE)
  → 앱 뱃지 즉시 업데이트

전 기기 동기화 (SSE)

서버 UNREAD_COUNT_CHANGED silent-sync
  → refetchQueries
  → WebBridge(WEB_REQ_SET_BADGE)
  → 앱 뱃지 동기화

전 기기 동기화 (FCM)

서버 badge_sync silent push
  → onMessage / 백그라운드 핸들러
  → data.unreadCount로 뱃지 업데이트

뱃지 UP (새 알림 도착)

SSE notification 이벤트
  → syncBadgeWithServer()
  → WebBridge(WEB_REQ_SET_BADGE)
  → 뱃지 증가

FCM notification push
  → APNs aps.badge
  → OS 네이티브 뱃지 업데이트

알림 목록 실시간 갱신

SSE notification 이벤트
  → refetchQueries(['notifications'])
  → 목록 즉시 갱신

작업 내용

WebBridge 메시지 타입 추가 (packages/core)

  • WEB_REQ_SET_BADGE 메시지 타입 추가
  • WebReqSetBadgeMessageT 타입 정의 및 WebBridgeMessageT 유니온에 추가

웹 → 앱 뱃지 동기화 (apps/web)

  • usePostNotificationsRead onSuccess에서 unreadCountWEB_REQ_SET_BADGE로 WebBridge 전송 (단건/전체 읽음 동일)
  • useGetNotifications에서 unreadCount 변경 시 WebBridge로 뱃지 동기화 (알림 페이지 진입 시 즉시 반영)
  • SSE silent-sync 이벤트 처리: TOURNAMENT_ITEM_PARSED / UNREAD_COUNT_CHANGED type 분기
  • SSE notification 이벤트 수신 시 알림 목록 즉시 갱신 + 뱃지 증가
  • SilentSyncSsePayloadT 타입 추가, NotificationTypeT에 누락 타입 추가

앱 SSE 버그 수정

Next.js rewrite 경유 시 SSE 버퍼링 문제로 앱 WebView에서 이벤트 미수신되던 문제 수정.
→ Route Handler(/api/notifications/subscribe) 경유로 통일, X-Client-Type 헤더 forwarding 추가


앱 뱃지 설정 (apps/app)

  • setAppBadgeCount(count) 함수 추가 (expo-notifications.setBadgeCountAsync 활용)
  • WEB_REQ_SET_BADGE 메시지 수신 시 OS 레벨 뱃지 즉시 업데이트
  • setNotificationHandler 추가 — 포그라운드에서 APNs aps.badge 처리 허용 (expo-notifications delegate 충돌 해결)
  • silent push 포그라운드 수신 시(onMessage) data.unreadCount로 뱃지 동기화
  • 백그라운드 FCM 핸들러에서 data.unreadCount로 뱃지 동기화

빌드 환경 (apps/app)

  • expo-notifications@^0.32.17 설치 (SDK 54 호환 버전)
  • iOS 최소 버전 15.1 → 16.4 상향 (expo-notifications podspec 요구사항)
  • app.json에 expo-notifications 플러그인 추가
  • iOS Podfile Firebase + useFrameworks: static 조합 빌드 에러 수정
    • $RNFirebaseAsStaticFramework = true 추가
    • RNFBAnalytics CLANG_ENABLE_MODULES = NO 설정

관련 이슈

closes #279
closes #287

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
depromeet Ready Ready Preview, Comment Jun 26, 2026 2:13pm

@github-actions github-actions Bot added APP Good for newcomers feature New feature or request labels Jun 23, 2026
@github-actions
github-actions Bot requested a review from iOdiO89 June 23, 2026 12:56
@github-actions github-actions Bot added the WEB label Jun 23, 2026
@github-actions

Copy link
Copy Markdown

No description provided.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kanghaeun, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 minutes and 47 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ee52fc6-aae5-44f3-abb3-e09b857f67e0

📥 Commits

Reviewing files that changed from the base of the PR and between c31ebbe and b1b0895.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • apps/app/app.json
  • apps/app/app/index.tsx
  • apps/app/index.js
  • apps/app/package.json
  • apps/app/utils/pushNotification.ts
  • apps/web/src/app/notification/_hooks/useGetNotifications.ts
  • apps/web/src/app/notification/_hooks/usePostNotificationsRead.ts
  • apps/web/src/hooks/useNotificationSSE.ts
  • apps/web/src/types/notification.ts
  • packages/core/src/consts/webBridge.ts
  • packages/core/src/index.ts
  • packages/core/src/types/pushNotification.ts
  • packages/core/src/types/webBridge.ts
📝 Walkthrough

Walkthrough

웹 알림 읽음 처리 후 WebBridge를 통해 앱 아이콘 뱃지 수를 동기화하는 기능을 추가했습니다. WEB_REQ_SET_BADGE 메시지 타입 및 상수를 공유 패키지에 정의하고, 웹의 onSuccess에서 메시지를 전송하며, 앱은 expo-notifications로 뱃지를 설정합니다. FCM 포그라운드 수신 시에도 뱃지를 자동 갱신합니다.

Changes

앱 아이콘 뱃지 동기화 (WebBridge WEB_REQ_SET_BADGE)

Layer / File(s) Summary
WebBridge 메시지 상수 및 타입 계약 정의
packages/core/src/consts/webBridge.ts, packages/core/src/types/pushNotification.ts, packages/core/src/types/webBridge.ts, packages/core/src/index.ts
WEBBRIDGE_MESSAGE_TYPEWEB_REQ_SET_BADGE 상수를 추가하고, payload: { count: number }를 가진 WebReqSetBadgeMessageT 타입을 정의해 WebBridgeMessageT 유니온에 포함시키고 패키지 외부로 export합니다.
웹 알림 읽음 처리 후 뱃지 갱신 메시지 전송
apps/web/src/app/notification/_hooks/usePostNotificationsRead.ts
onSuccess 콜백에서 쿼리 무효화 후 isWebview() 조건과 응답 데이터 존재 여부를 확인해 WebBridge.postMessageWEB_REQ_SET_BADGE 메시지와 unreadCount를 전송합니다.
앱 뱃지 설정 구현 및 WebBridge 수신 처리
apps/app/package.json, apps/app/app.json, apps/app/utils/pushNotification.ts, apps/app/app/index.tsx
expo-notifications를 의존성·플러그인으로 등록하고, setAppBadgeCount 함수를 구현합니다. handleWebMessageWEB_REQ_SET_BADGE 분기에서 해당 함수를 호출하며, FCM 포그라운드 메시지의 unreadCount를 수신할 때도 뱃지를 자동 갱신합니다.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 150, 255, 0.5)
    Note over Web,NativeApp: 경로 1 — 읽음 처리 후 즉각 뱃지 반영
    participant Web as Web (알림 읽음 처리)
    participant WebBridge as WebBridge
    participant NativeApp as NativeApp (index.tsx)
    participant pushNotification as pushNotification.ts
    participant Notifications as expo-notifications

    Web->>Web: onSuccess → invalidateQueries
    Web->>WebBridge: postMessage(WEB_REQ_SET_BADGE, { count })
    WebBridge->>NativeApp: 메시지 전달
    NativeApp->>pushNotification: setAppBadgeCount(count)
    pushNotification->>Notifications: setBadgeCountAsync(count)
  end

  rect rgba(100, 200, 150, 0.5)
    Note over FCM,Notifications: 경로 2 — FCM 포그라운드 수신 시 뱃지 자동 갱신
    participant FCM as FCM (포그라운드)
    FCM->>pushNotification: onMessage(remoteMessage)
    pushNotification->>pushNotification: unreadCount 추출 및 변환
    pushNotification->>Notifications: setBadgeCountAsync(unreadCount)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • depromeet/PIKI-Client#166: FCM 푸시 딥링크 연동을 위해 정의한 WebBridge 메시징·푸시 타입 체계를 이 PR이 동일 영역에서 WEB_REQ_SET_BADGE로 확장하는 직접적인 연관 관계가 있습니다.

Suggested reviewers

  • soyeong0115
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed PR의 구현이 #279 이슈의 요구사항을 완전히 충족합니다. 경로1(Read API 응답)과 경로2(Silent Push 동기화) 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 앱 뱃지 업데이트 기능 구현과 직접 관련되어 있으며, #279 이슈의 범위 내에 있습니다.
Title check ✅ Passed 앱 배지 동기화와 알림 갱신 구현이라는 핵심 변경점을 잘 요약한 제목입니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/app/utils/pushNotification.ts`:
- Around line 127-130: The code in the unreadCount handling block has two
issues: the comparison using !== undefined violates ESLint's no-undefined rule,
and the Number(unreadCount) conversion can produce NaN which gets passed to
setAppBadgeCount without validation. Replace the !== undefined check with an
ESLint-compliant approach (such as using typeof comparison or checking for a
truthy string representation), and add a validation step to ensure the converted
number is not NaN using Number.isNaN() before calling setAppBadgeCount to
prevent invalid badge values from being set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 73ba91a6-a4bd-4f9c-bfc2-78a74fc6ca92

📥 Commits

Reviewing files that changed from the base of the PR and between 69d903e and c31ebbe.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/app/app.json
  • apps/app/app/index.tsx
  • apps/app/package.json
  • apps/app/utils/pushNotification.ts
  • apps/web/src/app/notification/_hooks/usePostNotificationsRead.ts
  • packages/core/src/consts/webBridge.ts
  • packages/core/src/index.ts
  • packages/core/src/types/pushNotification.ts
  • packages/core/src/types/webBridge.ts

Comment on lines +127 to +130
const unreadCount = remoteMessage.data?.unreadCount;
if (unreadCount !== undefined) {
await setAppBadgeCount(Number(unreadCount));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

FCM unreadCount 처리에서 lint 에러와 비정상 배지 값 전파 가능성이 있습니다.

Line 128의 !== undefined는 현재 ESLint(no-undefined) 에러를 유발하고, Line 129는 숫자 검증 없이
Number(unreadCount)를 사용해 NaN이 배지 API로 전달될 수 있습니다.

수정 예시
-    const unreadCount = remoteMessage.data?.unreadCount;
-    if (unreadCount !== undefined) {
-      await setAppBadgeCount(Number(unreadCount));
-    }
+    const unreadCount = remoteMessage.data?.unreadCount;
+    if (unreadCount != null) {
+      const nextBadgeCount = Number(unreadCount);
+      if (Number.isFinite(nextBadgeCount) && nextBadgeCount >= 0) {
+        await setAppBadgeCount(Math.trunc(nextBadgeCount));
+      }
+    }
정적 분석 힌트(no-undefined)와 변경 라인의 데이터 흐름을 근거로 확인했습니다.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const unreadCount = remoteMessage.data?.unreadCount;
if (unreadCount !== undefined) {
await setAppBadgeCount(Number(unreadCount));
}
const unreadCount = remoteMessage.data?.unreadCount;
if (unreadCount != null) {
const nextBadgeCount = Number(unreadCount);
if (Number.isFinite(nextBadgeCount) && nextBadgeCount >= 0) {
await setAppBadgeCount(Math.trunc(nextBadgeCount));
}
}
🧰 Tools
🪛 ESLint

[error] 128-128: Unexpected use of undefined.

(no-undefined)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/app/utils/pushNotification.ts` around lines 127 - 130, The code in the
unreadCount handling block has two issues: the comparison using !== undefined
violates ESLint's no-undefined rule, and the Number(unreadCount) conversion can
produce NaN which gets passed to setAppBadgeCount without validation. Replace
the !== undefined check with an ESLint-compliant approach (such as using typeof
comparison or checking for a truthy string representation), and add a validation
step to ensure the converted number is not NaN using Number.isNaN() before
calling setAppBadgeCount to prevent invalid badge values from being set.

Source: Linters/SAST tools

@kanghaeun
kanghaeun marked this pull request as draft June 26, 2026 12:15
@iOdiO89
iOdiO89 force-pushed the feat/279-silent-push-badge-update branch from c31ebbe to cdb05d6 Compare June 26, 2026 12:19
@kanghaeun
kanghaeun force-pushed the feat/279-silent-push-badge-update branch from cdb05d6 to a4e8432 Compare June 26, 2026 13:49
@kanghaeun
kanghaeun marked this pull request as ready for review June 26, 2026 13:51
@kanghaeun kanghaeun changed the title feat: 알림 읽음 처리 시 앱 아이콘 뱃지 업데이트 feat: 앱 아이콘 뱃지 동기화 및 알림 실시간 갱신 구현 Jun 26, 2026
@kanghaeun
kanghaeun force-pushed the feat/279-silent-push-badge-update branch from a7d4b13 to a4e8432 Compare June 26, 2026 13:57
@kanghaeun
kanghaeun force-pushed the feat/279-silent-push-badge-update branch from a4e8432 to b1b0895 Compare June 26, 2026 14:11
@kanghaeun
kanghaeun merged commit 3f80e59 into dev Jun 26, 2026
5 checks passed
@kanghaeun
kanghaeun deleted the feat/279-silent-push-badge-update branch June 26, 2026 14:13
iOdiO89 pushed a commit that referenced this pull request Jul 19, 2026
* chore: expo-notifications 설치

* feat: WebBridge 타입 추가

* feat: pushNotification 타입 추가

* feat: WebBridgeMessageT 유니온에 추가

* feat: usePostNotificationsRead에서 unreadCount를 WebBridge로 전송

* feat: WEB_REQ_SET_BADGE 처리 + silent push 수신 시 뱃지 업데이트

* feat: WEB_REQ_SET_BADGE 메시지 처리

* chore: expo-notifications 플러그인 app.json에 추가

* chore: expo-notifications SDK 54 호환 버전으로 교체 및 iOS 최소 버전 16.4 상향

* feat: SSE silent-sync 이벤트 처리 및 알림 뱃지 동기화

* feat: 알림 목록 갱신 시 앱 뱃지 카운트 동기화

* feat: expo-notifications 포그라운드 뱃지 처리 및 FCM silent push 뱃지 동기화
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APP Good for newcomers feature New feature or request WEB

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: SSE silent-sync 이벤트 채널 전환 및 읽음 배지 인앱 실시간 동기화 feat: Silent Push 수신 시 앱 뱃지 업데이트

1 participant