Skip to content

웹/앱 푸시 알림 기능 추가#302

Open
H-sooyeon wants to merge 21 commits into
devfrom
feat/push-notification-#287
Open

웹/앱 푸시 알림 기능 추가#302
H-sooyeon wants to merge 21 commits into
devfrom
feat/push-notification-#287

Conversation

@H-sooyeon

@H-sooyeon H-sooyeon commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

요약 (연관 이슈 번호 포함)

크게 세 가지 흐름으로 나눌 수 있어요.

  1. 웹앱 푸시 알림 기능 추가
  2. 그룹별 알림 뮤트 설정 추가
  3. 함께 그룹함에서 그룹 상태(미읽음, 알림 뮤트) 시각화

Note

마이그레이션이 필요합니다.

Firebase 프로젝트 설정(서비스 계정 키, VAPID 키 등) 환경변수가 필요합니다. (노션 env 페이지에 Firebase 관련 키 올려놨습니다.)

추가로 백엔드 firebase 설정을 위한 json 파일이 필요해요. 이는 디스코드로 zip 파일과 비밀번호 보내드렸으니 다운받은 후 backend 내 루트에 두시면 됩니다. 해당 파일은 외부에 노출되지 않도록 주의해주세요.
(backend의 README.md가 위치하는 경로)

작업 내용 + 스크린샷

푸시 알림 인프라 구축

Firebase Cloud Messaging(FCM)만으로도 푸시 알림을 무료로 구현할 수 있어서, Firebase를 사용했어요.
Firebase로 웹과 안드로이드(Capacitor) 양쪽에 푸시 알림을 보낼 수 있는 기반을 만들었어요.

사용자가 로그인하면 디바이스 토큰을 서버에 등록하고, 그룹활동(기록 작성/수정/삭제, 멤버 입퇴장 등)이 발생하면 해당 그룹 멤버들에게 알림이 발송돼요.


웹 푸시 권한 요청 설계

브라우저 권한 다이얼로그를 언제 띄울까 고민이 됐어요.
로그인 직후 바로 요청하면 맥락이 없기에 유저가 거부할 가능성이 높고, 브라우저는 한 번 거부되면 재요청이 불가능해요.
그래서 웹에서는 설정 페이지에서 직접 알림 토글을 켤 때만 권한 다이얼로그가 뜨도록 설계했어요.

안드로이드(capacitor)는 앱 첫 실행 시 권한을 요청하는게 일반적으로 다른 서비스에서도 사용하는 방식이기에 그 방식을 따르기로 했어요.
이미 차단된 상태에서 다시 토글을 켜려 하면 시스템 알림 설정으로 안내하는 식으로 동작하게 했어요.


만료된 FCM 토큰 정리

앱 재설치나 장기 미사용 등으로 토큰이 만료된 경우, 발송 시도 결과에서 오류를 감지해 해당 토큰을 DB에서 삭제하도록 했어요.

Firebase 공식문서를 참고했어요.


그룹별 알림 뮤트 설정

전체 알림이 켜진 상태에서도 특정 그룹의 알림만 차단할 수 있도록 그룹 페이지 헤더에 알림 토글 버튼을 추가했어요.
권한 구분 없이 모든 멤버가 본인 기준으로 설정할 수 있어요.


함께 기록함에서 그룹 상태 표시

그룹 카드에서 두 가지 상태를 확인할 수 있도록 추가했어요.

  • 미읽음 뱃지: 마지막 방문 이후 새 활동이 있으면 표시돼요. 그룹 페이지 진입시 자동으로 읽음처리돼요.
  • 알림 뮤트 아이콘: 해당 그룹 알림을 꺼둔 경우 카드에 아이콘으로 표시돼요. 알림이 켜져 있는 경우 카카오톡을 참고해 알림 표시를 하지 않도록 했어요.

Note

iOS 푸시 알림은 Apple Developer Program 가입 없이는 불가능하다고 합니다...
Apple이 APNs를 통해서만 iOS에 푸시를 보낼 수 있게 해놨기 때문에, 개발자 계정으로 발급받은 키가 없으면 Firebase도 iOS로 푸시를 못 보낸다고 해요.
그래서 PWA도 안드로이드 기준으로 웹 푸시 알림을 지원하도록 해야할 것 같아요.

실제 걸린 시간

2day

작업하며 고민했던 점(선택)

테스트 실행 여부

  • 👍 네, 테스트했어요.
  • 🙅 아니요, 필요하지 않아요.
  • 🤯 아니요, 하지만 테스트가 필요해요.

그룹 내에서 다른 사람이 기록을 추가했을 때, 다른 사람에게 알림 메시지가 오는걸 확인했어요.

리뷰 참고사항

FCM 웹 푸시 테스트는 Chrome 등의 브라우저 시스템 알림 권한이 허용되어 있어야 정상 수신돼요.
Firebase 프로젝트 설정(서비스 계정 키, VAPID 키 등) 환경변수는 노션에 추가해놓을게요.

시크릿 탭에서는 알림 설정 자체가 거부되어 있기 때문에, 해당 탭에서의 알림은 오지 않아요.

시각 자료(이미지/영상, 있다면)

2026-07-08.12.19.57.mov

시크릿 탭에서 기록을 추가해 푸시된 알림

Summary by CodeRabbit

  • New Features

    • Added push notification support for web and Android, including notification settings, registration, and notification-driven navigation.
    • Added group notification controls, allowing members to mute or enable notifications.
    • Added unread activity indicators for groups and automatic read-state updates.
    • Group activity notifications now link directly to relevant groups or posts.
  • Bug Fixes

    • Improved record loading and navigation consistency across server and client views.
    • Enhanced notification click handling when the app is backgrounded or opened from Android.

@H-sooyeon H-sooyeon requested a review from IENFI July 7, 2026 05:09
@H-sooyeon H-sooyeon self-assigned this Jul 7, 2026
@H-sooyeon H-sooyeon added 🦉 feat New feature or request 🖥️ FE 프론트 작업 🛠️ BE 백엔드 작업 🧩 chore 코드 수정 외 환경 설정 labels Jul 7, 2026
@H-sooyeon H-sooyeon changed the title Feat/push notification #287 웹/앱 푸시 알림 기능 추가 Jul 7, 2026
H-sooyeon added 19 commits July 8, 2026 00:23
-firebase-admin, firebase 패키지 설치
- firebase-adminsdk.json 패턴을 .gitignore에 추가
- 프론트/백엔드 각각에 환경변수 example 키 추가
- FcmToken 엔티티: userId + platform(web/android) 조합으로 토큰 저장, 동일 조합은 upsert로 갱신(기기당 토큰 하나 유지)
- Firebase Admin SDK 초기화, sendToUser()로 다수 유저에게 FCM 멀티캣트 발송
- POST(토큰 등록, 갱신), DELET(토큰 삭제) 엔드포인트 추가
- GroupActivityService에 NotificationService를 주입해 그룹 활동 발생시 엑터를 제외한 나머지 그룹 멤버에게 FCM 푸시 알림을 fire-and-forget 방식으로 전송
- Firebase Messaging SDK를 PWA 환경에서 FCM 푸시 알림을 수신할 수 있도록 구현
- 백그라운드 메시지 수신 서비스 워커 추가
- Firebase 앱 초기화 및 Messaging 인스턴스 getter 추가
- 알림 권한 요청 -> FCM 토큰 발급 -> 백엔드 등록을 수행하는 훅 추가
- 로그인 상태일 때 훅 활성화 처리
- 네이티브 환경에서는 @capacitor/push-notifications의 requestPermissions, register, registration 이벤트를 통해 FCM 토큰을 발급받아 백엔드에 android 플랫폼으로 등록
- 웹(PWA) 환경에서는 기존 Firebase Web SDK getToken 경로를 유지
- API에 version 정보 추가
- FcmToken 엔티티를 @Index -> @unique로 변경해 upsert ON CONFLICT 오류 해결
- fcm_tokens unique constraint 마이그레이션 파일 추가
- getApiBaseUrl()이 서버 환경에서 v1을 포함한 getBackendApiBaseUrl()을 반환하고 fetchApi 내부에서 /api -> /v1 치환이 한 번 더 적용돼 /v1/v1/me 등이 발생하던 문제 수정
- getBackendOrigin(v1 미포함)을 반환하도록 변경
- sendToUsers에서 sendEach 응답을 확인해 UNREGISTERED/INVALID_ARGUMENT 오류가 반환된 토큰을 즉시 DB에서 삭제
- 브라우저 권한 취소나 앱 재설치 등으로 무효화된 토큰이 계속 남아있지 않도록 정리
- GroupMember에 notification_muted 컬럼 추가 (기본값은 false)
- 알림 수신 가능 여부 수정 PATCH 엔드포인트 추가
- role API 응답에 notificationMuted 포함
- dispatchNotification에서 notification_muted=true 멤버 수신 제외
- 그룹 헤더에 알림 토글 버튼 추가
- 그룹 진입시 읽음 처리로 /shared 미읽음 뱃지 자동 해제
- read PATCH 엔드포인트 추가
- 그룹 진입시 lastReadAt을 현재 시간으로 갱신
- 그룹 헤더 액션 컴포넌트 마운트시 markGroupAsRead API 호출
- 웹 토큰에는 data-only 메시지 전송, 안드로이드 토큰에는 notification 메시지 전송
  - notification 필드가 있으면 Firebase compat SW가 자동으로 알림 표시(FCM_MSG 래핑) +
    onBackgroundMessage도 호출 → 알림 중복 + notificationclick data 파싱 오류 발생
- 웹은 onBackgroundMessage에서만 알림 표시하도록 변경
- 푸시 알림 활성화 조건 변경: status === 'authenticated' → userType !== null으로 변경
- HydrationBoundary 추가로 서버 데이터를 클라이언트 QueryClient에 이식해 서버에서 패칭한 데이터를 클라이언트에서 그대로 재사용하도록해 스켈레톤 플래시 현상 개선
- React cache로 래핑되어 있어 동일 요청 내 중복 네트워크 요청 없이 재사용
- HydrationBoundary 추가 후 서버가 전체 컴포넌트 트리를 렌더링하면서 popover 내부 useId가 렌더 트리 순서 차이로 aria-controls 값이 불일치해 하이드레이션 에러 발생
- popover는 순수 인터랙티브 UI이기에 ssr에서 제외하도록 수정
@H-sooyeon H-sooyeon force-pushed the feat/push-notification-#287 branch from 8b5d8c0 to 4644c70 Compare July 7, 2026 15:35
@H-sooyeon H-sooyeon marked this pull request as ready for review July 7, 2026 15:52
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds Firebase Cloud Messaging across backend, web, and Android clients; introduces group notification mute/read state and activity notifications; updates notification-related UI and routing; and changes record detail loading to use React Query hydration.

Changes

Push notification platform

Layer / File(s) Summary
FCM token storage and delivery
backend/migrations/*, backend/src/modules/notification/*, backend/src/app.module.ts
Adds FCM token persistence, authenticated registration/removal endpoints, Firebase Admin initialization, and targeted notification delivery.
Group notification state and dispatch
backend/src/modules/group/*, backend/migrations/1783398050652-AddGroupMemberNotificationMuted-migration.ts
Stores mute/read state, exposes it in group responses, and dispatches activity notifications to eligible members.
Client push registration
frontend/src/hooks/*, frontend/src/lib/firebase.ts, frontend/public/firebase-messaging-sw.js, frontend/src/app/(main)/profile/_components/Setting.tsx, mobile-app/*
Adds web and Android token registration, permission controls, service-worker handling, and Capacitor Firebase configuration.
Group notification UI and cache state
frontend/src/app/(post)/group/*, frontend/src/app/(post)/shared/*, frontend/src/components/ui/RecordCard.tsx, frontend/src/lib/types/*
Adds group read/mute controls, cache synchronization, unread indicators, and muted notification visuals.
Android notification navigation
frontend/src/components/AndroidNotificationHandler.tsx, frontend/src/app/layout.tsx
Routes Android notification actions to group or record pages with authentication and query prefetch handling.

Record detail hydration

Layer / File(s) Summary
Record detail API and hydration
frontend/src/lib/api/api.ts, frontend/src/lib/api/records.ts, frontend/src/app/(post)/record/*
Rewrites server API paths, removes record scope from detail queries, and hydrates server-fetched record data into React Query.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NotificationController
  participant NotificationService
  participant FirebaseMessaging
  Client->>NotificationController: Register device token
  NotificationController->>NotificationService: Save token for user and platform
  NotificationService-->>Client: Registration success
  NotificationService->>FirebaseMessaging: Send group activity notification
  FirebaseMessaging-->>Client: Deliver push payload
Loading

Suggested reviewers: ienfi

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.73% 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
Title check ✅ Passed 제목이 웹/앱 푸시 알림 추가라는 핵심 변경을 잘 요약하며, 그룹별 알림 제어도 포함된 변경과 관련 있습니다.
Description check ✅ Passed 필수 섹션들이 모두 포함되어 있고 이슈 번호, 작업 내용, 소요 시간, 테스트 여부, 참고사항, 시각 자료까지 충족합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)

❌ Error committing Unit Tests locally.

  • Create PR with unit tests
  • Commit unit tests in branch feat/push-notification-#287

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.

@H-sooyeon

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/app/(post)/record/_components/RecordDetail.tsx (1)

107-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix CSS class typo: min-w-0, shrink should be min-w-0 shrink.

The comma makes min-w-0, an invalid Tailwind class — min-width: 0 is never applied. This is pre-existing but affects the 2-column layout rendering for span-1 blocks.

💚 Proposed fix
-                      className={cn(
-                        'min-w-0, shrink',
+                      className={cn(
+                        'min-w-0 shrink',
                         block.layout.col === 2 ? 'text-right' : 'text-left',
                       )}
🤖 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 `@frontend/src/app/`(post)/record/_components/RecordDetail.tsx around lines 107
- 109, Fix the className value in the RecordDetail component by replacing the
comma between `min-w-0` and `shrink` with a space, preserving both Tailwind
classes for the 2-column block layout.
🧹 Nitpick comments (1)
frontend/src/lib/api/notification.ts (1)

7-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the shared del helper here. It already exists in frontend/src/lib/api/api.ts, and removeFcmToken can pass the same DELETE body through its options.

🤖 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 `@frontend/src/lib/api/notification.ts` around lines 7 - 12, Update
removeFcmToken to use the shared del helper from the API utilities instead of
calling fetchApi directly, passing the existing platform payload through the
helper’s options while preserving the DELETE request behavior.
🤖 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 `@backend/migrations/1783344174180-CreateFcmToken-migration.ts`:
- Around line 8-9: Create the uuid-ossp extension in the migration before the
fcm_tokens table definition invokes uuid_generate_v4(), or change the id default
to the already-enabled gen_random_uuid() function; update the corresponding
migration rollback if needed to keep extension handling consistent.

In `@backend/package.json`:
- Line 55: Align the firebase-admin dependency with the runtime defined by the
backend Dockerfile: either update the Dockerfile’s Node image to Node 22+ or
change the firebase-admin entry in package.json to a compatible 13.x release.
Keep the selected dependency and runtime versions compatible.

In `@backend/src/modules/group/controller/group-management.controller.ts`:
- Around line 205-222: Replace the primitive `@Body`('muted') parameter in
toggleGroupNotification with a validation DTO containing a required boolean
muted field, and pass that DTO value to
groupManagementService.toggleGroupNotification. Add the corresponding `@ApiBody`
metadata so the request schema and required field are documented in Swagger.

In `@backend/src/modules/group/service/group-activity.service.ts`:
- Around line 65-69: Update the GROUP_NAME_UPDATE branch in the group activity
notification builder to provide a safe fallback when meta?.afterName is absent,
preventing "undefined" from appearing in the body while preserving the existing
message for valid names.

In `@backend/src/modules/notification/notification.controller.ts`:
- Around line 29-36: Define a dedicated RemoveFcmTokenDto containing the
platform field and its validation metadata, then update the removeToken method
to accept RemoveFcmTokenDto instead of Pick<RegisterFcmTokenDto, 'platform'> so
runtime request validation is applied.

In `@frontend/public/firebase-messaging-sw.js`:
- Around line 31-36: Update the Firebase compat script URLs in the service
worker’s importScripts calls to use the same release as the frontend firebase
dependency (currently ^12.15.0), or wire their version to the build-time
dependency value. Keep both firebase-app-compat.js and
firebase-messaging-compat.js aligned with the app’s Firebase SDK version.

In `@frontend/src/app/`(main)/profile/_components/Setting.tsx:
- Around line 147-153: Update the push-disable branch in Setting’s
removeFcmToken error handling so failures are surfaced to the user instead of
silently swallowed. Preserve the existing successful disable state updates, but
notify the user when token removal fails and avoid presenting the operation as
fully successful if the backend token remains registered.

In `@frontend/src/app/`(post)/group/_components/GroupHeaderActions.tsx:
- Around line 90-106: Add error handling and a pending state to the
toggleNotification mutation: define an onError callback that provides user
feedback using the component’s existing notification mechanism, and expose the
mutation’s pending/loading status. Update the notification toggle button to be
disabled while a mutation is pending, preventing concurrent clicks while
preserving the existing success cache updates.

In `@frontend/src/components/AndroidNotificationHandler.tsx`:
- Around line 38-49: Add a rejection handler to the dynamic import in the
Android notification setup, after registering the listener in the existing then
callback. Handle import failures through the component’s established
error-reporting mechanism, or otherwise consume the rejection without leaving an
unhandled promise.

In `@frontend/src/hooks/usePushNotification.ts`:
- Around line 27-38: Update registerAndroidToken’s Promise flow around the
PushNotifications registration listeners to include a timeout fallback that
resolves when neither registration nor registrationError fires. Ensure the
timeout and both event handlers share cleanup that removes all listeners and
prevents duplicate resolution, so subsequent calls cannot accumulate listeners.

---

Outside diff comments:
In `@frontend/src/app/`(post)/record/_components/RecordDetail.tsx:
- Around line 107-109: Fix the className value in the RecordDetail component by
replacing the comma between `min-w-0` and `shrink` with a space, preserving both
Tailwind classes for the 2-column block layout.

---

Nitpick comments:
In `@frontend/src/lib/api/notification.ts`:
- Around line 7-12: Update removeFcmToken to use the shared del helper from the
API utilities instead of calling fetchApi directly, passing the existing
platform payload through the helper’s options while preserving the DELETE
request behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6824d3e8-d7fe-4f58-99b1-c391ebed3423

📥 Commits

Reviewing files that changed from the base of the PR and between c2f3945 and 722c612.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (47)
  • .gitignore
  • backend/.env.example
  • backend/migrations/1783344174180-CreateFcmToken-migration.ts
  • backend/migrations/1783348447838-AddFcmTokenUniqueConstraint-migration.ts
  • backend/migrations/1783398050652-AddGroupMemberNotificationMuted-migration.ts
  • backend/package.json
  • backend/src/app.module.ts
  • backend/src/modules/group/controller/group-management.controller.ts
  • backend/src/modules/group/dto/get-group-permission.dto.ts
  • backend/src/modules/group/dto/get-groups.dto.ts
  • backend/src/modules/group/entity/group_member.entity.ts
  • backend/src/modules/group/group.module.ts
  • backend/src/modules/group/service/group-activity.service.ts
  • backend/src/modules/group/service/group-management.service.ts
  • backend/src/modules/group/service/group.service.ts
  • backend/src/modules/notification/dto/register-fcm-token.dto.ts
  • backend/src/modules/notification/entity/fcm-token.entity.ts
  • backend/src/modules/notification/notification.controller.ts
  • backend/src/modules/notification/notification.module.ts
  • backend/src/modules/notification/notification.service.ts
  • frontend/.env.example
  • frontend/package.json
  • frontend/public/firebase-messaging-sw.js
  • frontend/src/app/(main)/profile/_components/Setting.tsx
  • frontend/src/app/(post)/group/_components/GroupHeaderActions.tsx
  • frontend/src/app/(post)/record/[recordId]/loading.tsx
  • frontend/src/app/(post)/record/[recordId]/page.tsx
  • frontend/src/app/(post)/record/_components/RecordDetail.tsx
  • frontend/src/app/(post)/shared/_components/SharedHeaderActions.tsx
  • frontend/src/app/(post)/shared/_components/SharedRecords.tsx
  • frontend/src/app/AuthContext.tsx
  • frontend/src/app/layout.tsx
  • frontend/src/components/AndroidNotificationHandler.tsx
  • frontend/src/components/ui/RecordCard.tsx
  • frontend/src/hooks/usePushNotification.ts
  • frontend/src/lib/api/api.ts
  • frontend/src/lib/api/group.ts
  • frontend/src/lib/api/notification.ts
  • frontend/src/lib/api/records.ts
  • frontend/src/lib/firebase.ts
  • frontend/src/lib/mocks/mock.ts
  • frontend/src/lib/types/groupResponse.ts
  • frontend/src/lib/types/recordResponse.ts
  • mobile-app/android/app/capacitor.build.gradle
  • mobile-app/android/app/google-services.json
  • mobile-app/android/capacitor.settings.gradle
  • mobile-app/package.json
💤 Files with no reviewable changes (1)
  • frontend/src/app/(post)/record/[recordId]/loading.tsx

Comment on lines +8 to +9
`CREATE TABLE "fcm_tokens" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for existing uuid_generate_v4 usage or extension enablement in the codebase
rg -n "uuid_generate_v4\|uuid-ossp\|uuid-ossp" --type sql
rg -n "uuid_generate_v4" backend/migrations/

Repository: boostcampwm2025/web25-ittda

Length of output: 165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== migration file =="
git ls-files 'backend/migrations/*CreateFcmToken*' 'backend/migrations/*.ts' | sed -n '1,20p'
echo

mig="$(git ls-files 'backend/migrations/*CreateFcmToken*' | head -n 1)"
if [ -n "${mig:-}" ]; then
  echo "== outline: $mig =="
  ast-grep outline "$mig" --view expanded || true
  echo
  echo "== file excerpt: $mig =="
  nl -ba "$mig" | sed -n '1,140p'
fi
echo

echo "== search for uuid-ossp / uuid_generate_v4 / extension creation =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'uuid_generate_v4|uuid-ossp|CREATE EXTENSION|uuid_generate_v1|gen_random_uuid' \
  .

Repository: boostcampwm2025/web25-ittda

Length of output: 1953


🏁 Script executed:

#!/bin/bash
set -euo pipefail

mig='backend/migrations/1783344174180-CreateFcmToken-migration.ts'

echo "== file excerpt =="
sed -n '1,120p' "$mig"
echo

echo "== search for extension setup in all migrations =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'CREATE EXTENSION|uuid-ossp|uuid_generate_v4|gen_random_uuid' \
  backend/migrations backend

Repository: boostcampwm2025/web25-ittda

Length of output: 20019


Create uuid-ossp before calling uuid_generate_v4(). backend/migrations/1767732378313-init-schema.ts only enables pgcrypto, so this migration will fail on a clean database unless uuid-ossp is added here or the id default is switched to gen_random_uuid().

🤖 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 `@backend/migrations/1783344174180-CreateFcmToken-migration.ts` around lines 8
- 9, Create the uuid-ossp extension in the migration before the fcm_tokens table
definition invokes uuid_generate_v4(), or change the id default to the
already-enabled gen_random_uuid() function; update the corresponding migration
rollback if needed to keep extension handling consistent.

Comment thread backend/package.json
"class-validator": "^0.14.3",
"cookie-parser": "^1.4.7",
"dotenv": "^17.2.3",
"firebase-admin": "^14.1.0",

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

🧩 Analysis chain

🌐 Web query:

What is the latest stable version of the firebase-admin npm package?

💡 Result:

The latest stable version of the firebase-admin npm package as of July 13, 2026, is 14.1.0, which was published on June 24, 2026 [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the package metadata around firebase-admin and Node engine requirements.
file="backend/package.json"
wc -l "$file"
sed -n '1,140p' "$file"

Repository: boostcampwm2025/web25-ittda

Length of output: 6317


🌐 Web query:

firebase-admin 14.1.0 Node.js engine requirement release notes

💡 Result:

The Firebase Admin Node.js SDK version 14.1.0, released on June 24, 2026, officially requires Node.js 22 or higher [1][2][3]. This version includes a shift in support, as the SDK has dropped compatibility for Node.js 18 and 20 [4]. Developers are advised to use environments running Node.js 22 and above when deploying this version of the Admin SDK [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any declared Node.js runtime/engine target in the repository.
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  -e '"engines"\s*:' \
  -e 'NODE_VERSION' \
  -e 'node\s*[0-9]+\b' \
  -e '22(\.x)?' \
  -e '20(\.x)?' \
  -e '18(\.x)?' \
  -e 'FROM\s+node:' \
  -e 'runtime.*node' \
  .

Repository: boostcampwm2025/web25-ittda

Length of output: 50384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look only at backend/runtime-related files for a declared Node target.
printf '\n[backend files]\n'
git ls-files backend | sed -n '1,200p'

printf '\n[Node target mentions in backend + deployment files]\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  -e '"engines"\s*:' \
  -e 'node-version' \
  -e 'FROM\s+node:' \
  -e 'NODE_VERSION' \
  -e 'nvmrc' \
  backend .github docker-compose.yml Dockerfile* README.md

Repository: boostcampwm2025/web25-ittda

Length of output: 10805


Keep firebase-admin on a Node 20-compatible major
firebase-admin 14.1.0 requires Node.js 22+, but backend/Dockerfile still uses node:20-slim. Either bump the backend runtime to Node 22+ or stay on a 13.x release.

🤖 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 `@backend/package.json` at line 55, Align the firebase-admin dependency with
the runtime defined by the backend Dockerfile: either update the Dockerfile’s
Node image to Node 22+ or change the firebase-admin entry in package.json to a
compatible 13.x release. Keep the selected dependency and runtime versions
compatible.

Comment on lines +205 to +222
@Patch(':groupId/members/me/notification')
@ApiOperation({
summary: '그룹 알림 설정 토글',
description:
'권한에 관계없이 모든 멤버가 해당 그룹의 알림을 켜고 끌 수 있습니다.',
})
@ApiParam({ name: 'groupId', description: '그룹 ID' })
async toggleGroupNotification(
@User() user: MyJwtPayload,
@Param('groupId') groupId: string,
@Body('muted') muted: boolean,
): Promise<void> {
await this.groupManagementService.toggleGroupNotification(
user.sub,
groupId,
muted,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use a validation DTO for the muted body field.

@Body('muted') muted: boolean bypasses NestJS validation entirely — there is no class for the ValidationPipe to validate against, so muted can be undefined, a string, or any arbitrary value. If undefined is passed to the service, it could produce a database error when assigning to the non-nullable notificationMuted column. Additionally, the endpoint lacks @ApiBody for Swagger documentation.

🛡️ Proposed fix: create a DTO and apply it
+// In a new or existing DTO file, e.g. toggle-group-notification.dto.ts
+import { IsBoolean } from 'class-validator';
+
+export class ToggleGroupNotificationDto {
+  `@IsBoolean`()
+  muted!: boolean;
+}

Then update the controller:

-  `@Patch`(':groupId/members/me/notification')
+  `@Patch`(':groupId/members/me/notification')
   `@ApiOperation`({
     summary: '그룹 알림 설정 토글',
     description:
       '권한에 관계없이 모든 멤버가 해당 그룹의 알림을 켜고 끌 수 있습니다.',
   })
   `@ApiParam`({ name: 'groupId', description: '그룹 ID' })
+  `@ApiBody`({ type: ToggleGroupNotificationDto })
   async toggleGroupNotification(
     `@User`() user: MyJwtPayload,
     `@Param`('groupId') groupId: string,
-    `@Body`('muted') muted: boolean,
+    `@Body`() dto: ToggleGroupNotificationDto,
   ): Promise<void> {
     await this.groupManagementService.toggleGroupNotification(
       user.sub,
       groupId,
-      muted,
+      dto.muted,
     );
   }
📝 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
@Patch(':groupId/members/me/notification')
@ApiOperation({
summary: '그룹 알림 설정 토글',
description:
'권한에 관계없이 모든 멤버가 해당 그룹의 알림을 켜고 끌 수 있습니다.',
})
@ApiParam({ name: 'groupId', description: '그룹 ID' })
async toggleGroupNotification(
@User() user: MyJwtPayload,
@Param('groupId') groupId: string,
@Body('muted') muted: boolean,
): Promise<void> {
await this.groupManagementService.toggleGroupNotification(
user.sub,
groupId,
muted,
);
}
`@Patch`(':groupId/members/me/notification')
`@ApiOperation`({
summary: '그룹 알림 설정 토글',
description:
'권한에 관계없이 모든 멤버가 해당 그룹의 알림을 켜고 끌 수 있습니다.',
})
`@ApiParam`({ name: 'groupId', description: '그룹 ID' })
`@ApiBody`({ type: ToggleGroupNotificationDto })
async toggleGroupNotification(
`@User`() user: MyJwtPayload,
`@Param`('groupId') groupId: string,
`@Body`() dto: ToggleGroupNotificationDto,
): Promise<void> {
await this.groupManagementService.toggleGroupNotification(
user.sub,
groupId,
dto.muted,
);
}
🤖 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 `@backend/src/modules/group/controller/group-management.controller.ts` around
lines 205 - 222, Replace the primitive `@Body`('muted') parameter in
toggleGroupNotification with a validation DTO containing a required boolean
muted field, and pass that DTO value to
groupManagementService.toggleGroupNotification. Add the corresponding `@ApiBody`
metadata so the request schema and required field are documented in Swagger.

Comment on lines +65 to +69
case GroupActivityType.GROUP_NAME_UPDATE:
return {
title: '그룹 이름 변경',
body: `그룹 이름이 "${meta?.afterName as string}"(으)로 변경되었습니다.`,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against undefined in the group name change notification body.

meta?.afterName as string produces undefined at runtime if meta is null or afterName is missing, resulting in a notification body like 그룹 이름이 "undefined"(으)로 변경되었습니다. While the current caller always provides afterName, this is fragile if new callers are added.

🛡️ Proposed fix: add a fallback
     body: `그룹 이름이 "${meta?.afterName as string}"(으)로 변경되었습니다.`,
+    // Replace with:
+    body: `그룹 이름이 "${(meta?.afterName as string) ?? '알 수 없음'}"(으)로 변경되었습니다.`,
🤖 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 `@backend/src/modules/group/service/group-activity.service.ts` around lines 65
- 69, Update the GROUP_NAME_UPDATE branch in the group activity notification
builder to provide a safe fallback when meta?.afterName is absent, preventing
"undefined" from appearing in the body while preserving the existing message for
valid names.

Comment on lines +29 to +36
@Delete('fcm-token')
async removeToken(
@Req() req: RequestWithUser,
@Body() dto: Pick<RegisterFcmTokenDto, 'platform'>,
) {
await this.notificationService.removeToken(req.user.sub, dto.platform);
return { success: true };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== controller ==\n'
sed -n '1,220p' backend/src/modules/notification/notification.controller.ts

printf '\n== register dto ==\n'
sed -n '1,220p' backend/src/modules/notification/dto/register-fcm-token.dto.ts

printf '\n== notification service ==\n'
sed -n '1,220p' backend/src/modules/notification/notification.service.ts

printf '\n== validation config search ==\n'
rg -n "ValidationPipe|transform:|whitelist|forbidNonWhitelisted|enableImplicitConversion" backend/src -g '!**/dist/**'

Repository: boostcampwm2025/web25-ittda

Length of output: 6067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' backend/src/modules/notification/notification.controller.ts
printf '\n---\n'
sed -n '1,120p' backend/src/modules/notification/dto/register-fcm-token.dto.ts
printf '\n---\n'
rg -n "new ValidationPipe|ValidationPipe\\(" backend/src backend/main.ts backend/src/main.ts -g '!**/dist/**'

Repository: boostcampwm2025/web25-ittda

Length of output: 1607


Replace Pick<RegisterFcmTokenDto, 'platform'> with a dedicated DTO. Pick is erased at runtime, so this DELETE body is treated as Object and the platform validator won’t run here. A small RemoveFcmTokenDto keeps the request validated.

🤖 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 `@backend/src/modules/notification/notification.controller.ts` around lines 29
- 36, Define a dedicated RemoveFcmTokenDto containing the platform field and its
validation metadata, then update the removeToken method to accept
RemoveFcmTokenDto instead of Pick<RegisterFcmTokenDto, 'platform'> so runtime
request validation is applied.

Comment on lines +31 to +36
importScripts(
'https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js',
);
importScripts(
'https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the firebase version in package.json
rg -n '"firebase"' frontend/package.json
# Check if there's a build step that could inject the version into the SW
rg -rn 'firebase-messaging-sw' frontend/ --glob '!node_modules' -l

Repository: boostcampwm2025/web25-ittda

Length of output: 266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package.json version ==\n'
sed -n '35,50p' frontend/package.json

printf '\n== firebase messaging service worker ==\n'
wc -l frontend/public/firebase-messaging-sw.js
cat -n frontend/public/firebase-messaging-sw.js | sed -n '1,140p'

printf '\n== usePushNotification.ts ==\n'
wc -l frontend/src/hooks/usePushNotification.ts
cat -n frontend/src/hooks/usePushNotification.ts | sed -n '1,240p'

printf '\n== app/layout.tsx ==\n'
wc -l frontend/src/app/layout.tsx
cat -n frontend/src/app/layout.tsx | sed -n '1,240p'

Repository: boostcampwm2025/web25-ittda

Length of output: 19600


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== firebase-related imports ==\n'
rg -n "firebase/(app|messaging)|firebasejs/" frontend/src frontend/public -g '!**/node_modules/**'

printf '\n== lib/firebase.ts ==\n'
wc -l frontend/src/lib/firebase.ts
cat -n frontend/src/lib/firebase.ts | sed -n '1,220p'

printf '\n== package lock references to firebase ==\n'
rg -n '"firebase":|firebasejs/10\.14\.1|firebasejs/12\.15\.0' frontend/package-lock.json frontend/pnpm-lock.yaml frontend/yarn.lock 2>/dev/null || true

Repository: boostcampwm2025/web25-ittda

Length of output: 1790


🌐 Web query:

Firebase Messaging service worker importScripts version must match app package version compat modular

💡 Result:

Yes, it is considered best practice and often necessary to ensure the version of the Firebase SDK loaded via importScripts in your service worker matches the version used in your main application [1][2]. When using the CDN-based importScripts approach to load Firebase libraries in a service worker (e.g., firebase-messaging-sw.js), you are typically using the compatibility (compat) layer [1][3]. Mismatched versions between your main app and your service worker can lead to unexpected runtime errors, initialization failures, or an inability to receive background notifications [4][2]. Key points regarding Firebase Messaging service workers: 1. Version Matching: Always ensure the version number in your importScripts URLs (e.g., https://www.gstatic.com/firebasejs/X.Y.Z/...) aligns exactly with the version of the Firebase SDK installed in your project's package.json [1][2]. 2. Compat vs. Modular: - If you use importScripts in your service worker, you must use the -compat versions of the Firebase scripts (e.g., firebase-app-compat.js and firebase-messaging-compat.js) [1][5][3]. The standard modular SDK scripts are ES modules and will fail to load via importScripts unless they are bundled as part of your application build process [1][3]. - If you want to use the modern modular SDK (ES imports) directly in your service worker, you must configure your application bundler to include the service worker as a build artifact or register the service worker as a module using the { type: 'module' } option [1][5][6]. 3. Maintenance: Since the service worker file is often managed separately from the main application bundle, it is easy to forget to update it during SDK upgrades [2]. Establishing a practice of updating the versions in firebase-messaging-sw.js whenever you update your main Firebase dependencies is essential to prevent breaking changes [4][2].

Citations:


Align the Firebase SDK version in the service worker frontend/public/firebase-messaging-sw.js:31-36 hardcodes 10.14.1, while frontend/package.json depends on firebase ^12.15.0. Keep the compat scripts on the same Firebase release as the app, or inject the version at build time, to avoid background messaging drift.

🤖 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 `@frontend/public/firebase-messaging-sw.js` around lines 31 - 36, Update the
Firebase compat script URLs in the service worker’s importScripts calls to use
the same release as the frontend firebase dependency (currently ^12.15.0), or
wire their version to the build-time dependency value. Keep both
firebase-app-compat.js and firebase-messaging-compat.js aligned with the app’s
Firebase SDK version.

Comment on lines +147 to +153
if (pushEnabled) {
try {
await removeFcmToken(platform);
} catch {}
localStorage.setItem('push_notifications_disabled', 'true');
setPushEnabled(false);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show user feedback when removeFcmToken fails during push disable.

The catch {} silently swallows errors. If the API call fails, the UI shows push as disabled but the backend still has the token, so the user may continue receiving notifications without knowing the disable didn't fully take effect.

🛡️ Proposed fix to add user feedback on failure
     if (pushEnabled) {
       try {
         await removeFcmToken(platform);
+      } catch {
+        toast.error('알림 해제에 실패했습니다. 잠시 후 다시 시도해 주세요.');
+        return;
       }
-      } catch {}
       localStorage.setItem('push_notifications_disabled', 'true');
       setPushEnabled(false);
       return;
     }
📝 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
if (pushEnabled) {
try {
await removeFcmToken(platform);
} catch {}
localStorage.setItem('push_notifications_disabled', 'true');
setPushEnabled(false);
return;
if (pushEnabled) {
try {
await removeFcmToken(platform);
} catch {
toast.error('알림 해제에 실패했습니다. 잠시 후 다시 시도해 주세요.');
return;
}
localStorage.setItem('push_notifications_disabled', 'true');
setPushEnabled(false);
return;
🤖 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 `@frontend/src/app/`(main)/profile/_components/Setting.tsx around lines 147 -
153, Update the push-disable branch in Setting’s removeFcmToken error handling
so failures are surfaced to the user instead of silently swallowed. Preserve the
existing successful disable state updates, but notify the user when token
removal fails and avoid presenting the operation as fully successful if the
backend token remains registered.

Comment on lines +90 to +106
const { mutate: toggleNotification } = useMutation({
mutationFn: (muted: boolean) => toggleGroupNotification(groupId, muted),
onSuccess: (_, muted) => {
queryClient.setQueryData(
['group', groupId, 'me', 'role'],
(prev: typeof roleData) =>
prev ? { ...prev, notificationMuted: muted } : prev,
);
queryClient.setQueryData<GroupSummary[]>(['shared'], (prev) =>
prev
? prev.map((g) =>
g.groupId === groupId ? { ...g, notificationMuted: muted } : g,
)
: prev,
);
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add error handling and loading state to the notification toggle mutation.

Two issues with the toggleNotification mutation:

  1. No onError handler — if the API call fails, the user gets no feedback and doesn't know the toggle didn't work.
  2. No disabled state — rapid clicks fire concurrent mutations. Since onSuccess callbacks fire in completion order (not invocation order), the cache can end up in a state that doesn't match the user's last action.
🔧 Proposed fix
   const { mutate: toggleNotification, isPending } = useMutation({
     mutationFn: (muted: boolean) => toggleGroupNotification(groupId, muted),
     onSuccess: (_, muted) => {
       queryClient.setQueryData(
         ['group', groupId, 'me', 'role'],
         (prev: typeof roleData) =>
           prev ? { ...prev, notificationMuted: muted } : prev,
       );
       queryClient.setQueryData<GroupSummary[]>(['shared'], (prev) =>
         prev
           ? prev.map((g) =>
               g.groupId === groupId ? { ...g, notificationMuted: muted } : g,
             )
           : prev,
       );
     },
+    onError: () => {
+      toast.error('알림 설정 변경에 실패했습니다.');
+    },
   });

And for the button:

         <button
           onClick={() => toggleNotification(!notificationMuted)}
+          disabled={isPending}
           className="cursor-pointer p-2 sm:p-2.5 rounded-xl transition-colors active:scale-95 dark:bg-white/5 dark:text-gray-500 bg-gray-50 text-gray-400"
           title={notificationMuted ? '알림 켜기' : '알림 끄기'}
         >
🤖 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 `@frontend/src/app/`(post)/group/_components/GroupHeaderActions.tsx around
lines 90 - 106, Add error handling and a pending state to the toggleNotification
mutation: define an onError callback that provides user feedback using the
component’s existing notification mechanism, and expose the mutation’s
pending/loading status. Update the notification toggle button to be disabled
while a mutation is pending, preventing concurrent clicks while preserving the
existing success cache updates.

Comment on lines +38 to +49
if (platform === 'android') {
import('@capacitor/push-notifications').then(({ PushNotifications }) => {
PushNotifications.addListener(
'pushNotificationActionPerformed',
(action) => {
handleNotificationAction(
action.notification.data as Record<string, string> | undefined,
);
},
);
});
}

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 | 🟡 Minor | ⚡ Quick win

Add .catch() to the dynamic import to prevent unhandled promise rejection.

If @capacitor/push-notifications fails to load (e.g., package missing, network error), the promise rejection is unhandled. This won't crash the app in most browsers but pollutes error logs and may surface in monitoring.

🛡️ Proposed fix
  if (platform === 'android') {
    import('`@capacitor/push-notifications`').then(({ PushNotifications }) => {
      PushNotifications.addListener(
        'pushNotificationActionPerformed',
        (action) => {
          handleNotificationAction(
            action.notification.data as Record<string, string> | undefined,
          );
        },
      );
-   });
+   }).catch(() => {});
  }
📝 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
if (platform === 'android') {
import('@capacitor/push-notifications').then(({ PushNotifications }) => {
PushNotifications.addListener(
'pushNotificationActionPerformed',
(action) => {
handleNotificationAction(
action.notification.data as Record<string, string> | undefined,
);
},
);
});
}
if (platform === 'android') {
import('`@capacitor/push-notifications`').then(({ PushNotifications }) => {
PushNotifications.addListener(
'pushNotificationActionPerformed',
(action) => {
handleNotificationAction(
action.notification.data as Record<string, string> | undefined,
);
},
);
}).catch(() => {});
}
🤖 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 `@frontend/src/components/AndroidNotificationHandler.tsx` around lines 38 - 49,
Add a rejection handler to the dynamic import in the Android notification setup,
after registering the listener in the existing then callback. Handle import
failures through the component’s established error-reporting mechanism, or
otherwise consume the rejection without leaving an unhandled promise.

Comment on lines +27 to +38
await new Promise<void>((resolve) => {
PushNotifications.addListener('registration', async ({ value: token }) => {
await registerFcmToken(token, 'android').catch(() => {});
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.addListener('registrationError', async () => {
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.register();
});

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 | 🟡 Minor | ⚡ Quick win

Add a timeout to registerAndroidToken to prevent indefinite hangs and listener leaks.

If neither the registration nor registrationError event fires (e.g., push service unavailable, plugin fails silently), the Promise never resolves and the Capacitor listeners remain attached. Subsequent calls would stack additional listeners, risking duplicate token registrations.

⏱️ Proposed fix to add a timeout with listener cleanup
 export async function registerAndroidToken() {
   const { PushNotifications } = await import('`@capacitor/push-notifications`');
 
   const result = await PushNotifications.requestPermissions();
   if (result.receive !== 'granted') return;
 
-  await new Promise<void>((resolve) => {
+  await Promise.race([
+    new Promise<void>((resolve) => {
       PushNotifications.addListener('registration', async ({ value: token }) => {
         await registerFcmToken(token, 'android').catch(() => {});
         await PushNotifications.removeAllListeners();
         resolve();
       });
       PushNotifications.addListener('registrationError', async () => {
         await PushNotifications.removeAllListeners();
         resolve();
       });
       PushNotifications.register();
-  });
+    }),
+    new Promise<void>((resolve) =>
+      setTimeout(() => {
+        PushNotifications.removeAllListeners();
+        resolve();
+      }, 10000),
+    ),
+  ]);
 }
📝 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
await new Promise<void>((resolve) => {
PushNotifications.addListener('registration', async ({ value: token }) => {
await registerFcmToken(token, 'android').catch(() => {});
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.addListener('registrationError', async () => {
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.register();
});
await Promise.race([
new Promise<void>((resolve) => {
PushNotifications.addListener('registration', async ({ value: token }) => {
await registerFcmToken(token, 'android').catch(() => {});
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.addListener('registrationError', async () => {
await PushNotifications.removeAllListeners();
resolve();
});
PushNotifications.register();
}),
new Promise<void>((resolve) =>
setTimeout(() => {
PushNotifications.removeAllListeners();
resolve();
}, 10000),
),
]);
🤖 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 `@frontend/src/hooks/usePushNotification.ts` around lines 27 - 38, Update
registerAndroidToken’s Promise flow around the PushNotifications registration
listeners to include a timeout fallback that resolves when neither registration
nor registrationError fires. Ensure the timeout and both event handlers share
cleanup that removes all listeners and prevents duplicate resolution, so
subsequent calls cannot accumulate listeners.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Note

Unit test generation is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Request timed out after 900000ms (requestId=d0832b99-59d4-43c5-967e-6f0f69f6420e)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠️ BE 백엔드 작업 🧩 chore 코드 수정 외 환경 설정 🖥️ FE 프론트 작업 🦉 feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant