Skip to content

Feat(client): FCM 연동 및 fcm 토큰 전달#89

Merged
jllee000 merged 2 commits intodevelopfrom
feat/#88/onboarding-FCM
Sep 13, 2025
Merged

Feat(client): FCM 연동 및 fcm 토큰 전달#89
jllee000 merged 2 commits intodevelopfrom
feat/#88/onboarding-FCM

Conversation

@jllee000
Copy link
Collaborator

@jllee000 jllee000 commented Sep 13, 2025

📌 Related Issues

📄 Tasks

  1. firebase 설치
  2. FCM 연동 토큰 받아오기 및 서버에 전달

⭐ PR Point (To Reviewer)

FCM 키랑 계정정보 env 세팅 반드시 해주세요!

📷 Screenshot

Summary by CodeRabbit

  • 신규 기능

    • 온보딩 중 푸시 알림(FCM) 권한 요청 및 토큰 발급을 지원합니다.
    • 발급된 FCM 토큰을 온보딩 최종 단계에서 서버로 전송합니다.
    • 권한 거부/오류 시 사용자 알림과 기본 오류 처리(로그)를 제공합니다.
  • 버그 수정

    • 알림 토큰이 없을 경우(null)에도 가입 흐름이 계속되도록 안정성 개선.
  • 작업

    • 클라이언트에 Firebase 의존성 추가 및 환경변수 기반 Firebase 설정 도입.

@jllee000 jllee000 self-assigned this Sep 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 13, 2025

Walkthrough

클라이언트에 Firebase 의존성 및 Vite 기반 firebaseConfig를 추가했고, 온보딩 MainCard 컴포넌트에 FCM 토큰 요청/저장 로직을 통합하며 회원가입 요청의 fcmToken 타입을 string | null로 확장했습니다.

Changes

Cohort / File(s) Change Summary
Firebase 의존성 & 설정
apps/client/package.json, apps/client/src/firebase-config.ts
firebase@^12.2.1 의존성 추가. Vite 환경변수로부터 값을 읽는 firebaseConfig 상수 신규 추가 및 export.
온보딩 FCM 연동
apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx
Firebase 앱 초기화 및 Messaging 인스턴스 생성. 알림 권한 요청 및 getToken으로 FCM 토큰 획득 로직 추가(비동기화된 nextStep 처리). 단계 3에서 토큰 요청, 최종 제출(step 5)에서 실제 fcmToken 전송. 에러 처리 및 콘솔/알림 로깅 추가.
API 타입 변경
apps/client/src/shared/apis/axios.ts
postSignUpRequestfcmToken 타입을 string → `string

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 사용자
  participant MC as MainCard (Onboarding)
  participant FB as Firebase App
  participant MSG as Firebase Messaging
  participant API as 백엔드 API

  U->>MC: 온보딩 진행
  MC->>FB: initializeApp(firebaseConfig)
  MC->>MSG: getMessaging(FB)
  alt Step 3: 알림 권한 요청
    MC->>U: Notification 권한 요청
    alt 허용
      MC->>MSG: getToken(VAPID 키)
      MSG-->>MC: FCM 토큰 반환
      MC->>MC: fcmToken 상태에 저장
      MC->>U: 다음 단계로 진행
    else 거부/실패
      MC->>U: 오류/안내 표시
    end
  end
  alt Step 5: 최종 제출
    MC->>API: signUp(payload with fcmToken|null)
    API-->>MC: 응답
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🛠️ Feature, api

Suggested reviewers

  • jjangminii
  • constantly-dev

Poem

깡총깡총, 토큰 한 알 줍다 🥕
권한 묻고, 푸시의 길을 잇고
스텝 셋에서 깡충, 스텝 다섯에 쿵!
내 꼬리로 전하는 핀백의 소식 — 힙힙, 알림이 와요.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed 현재 제목 "Feat(client): FCM 연동 및 fcm 토큰 전달"은 PR의 핵심 변경사항인 온보딩에서의 FCM 연동과 토큰 전달을 명확히 요약하고 있어 짧고 이해하기 쉬운 단일 문장으로 적절합니다.
Linked Issues Check ✅ Passed 변경사항은 firebase 의존성 추가, firebase-config.ts 추가, 온보딩 MainCard에서의 FCM 토큰 요청 로직 및 최종 API에 토큰 전달, 그리고 API 타입의 fcmToken 확장 등으로 링크된 이슈(#88)의 '온보딩 FCM 연동 및 토큰 전송' 핵심 요구사항을 충족합니다; 다만 서비스워커 등록/파일과 VAPID 키·환경변수 관련 문서화는 PR에 포함되어 있지 않아 배포 전 추가 확인이 필요합니다.
Out of Scope Changes Check ✅ Passed 검토된 변경은 모두 FCM 연동 목적과 직접 관련된 파일들(패키지 의존성, firebase 설정, 온보딩 컴포넌트 변경, API 타입 확장)로 보이며 링크된 이슈 범위를 벗어난 변경은 발견되지 않았습니다.
Description Check ✅ Passed PR 설명은 템플릿 구조를 따르고 있으며 Related Issues( close #88 ), 작업 목록(Tasks)과 리뷰어에 대한 주의사항(PR Point)을 포함해 주요 정보가 잘 기재되어 있어 전반적으로 양호합니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#88/onboarding-FCM

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 and usage tips.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Sep 13, 2025
@github-actions
Copy link

github-actions bot commented Sep 13, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (1)

125-133: setState 직후 값 사용 버그: 잘못된 remindDefault 전송 가능성

setRemindTime 직후 remindTime을 사용하면 이전 값이 전송됩니다. 로컬 변수로 계산한 값을 즉시 사용하세요.

-      const raw = AlarmsType[alarmSelected - 1].time;
-      setRemindTime(normalizeTime(raw));
+      const raw = AlarmsType[alarmSelected - 1].time;
+      const normalized = normalizeTime(raw);
+      setRemindTime(normalized);
       postSignData({
-            "email": "tesdfdfsst@gmail.com",  // TODO : 익스텐션에게서 메일 받기
-            "remindDefault": remindTime, 
-            "fcmToken": fcmToken,
+            email: "tesdfdfsst@gmail.com",  // TODO : 익스텐션에게서 메일 받기
+            remindDefault: normalized,
+            fcmToken: fcmToken,
         },
🧹 Nitpick comments (5)
apps/client/src/shared/apis/axios.ts (1)

27-27: 서버 스키마 확인: null 전송 vs 필드 생략

fcmToken: string | null로 변경은 합리적이지만, 서버가 null을 명시적으로 기대하는지(또는 필드 자체 생략을 선호하는지) 확인이 필요합니다. 만약 생략을 허용한다면 optional 필드가 더 깔끔합니다.

가능 시 인터페이스를 아래처럼 단순화하세요:

-export interface postSignUpRequest {
+export interface postSignUpRequest {
   email: string,
   remindDefault: string,
-  fcmToken: string | null
+  fcmToken?: string
 }

그리고 호출부에서는 fcmToken ?? undefined로 넘기거나, null이면 키 자체를 제거하세요.

apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (4)

108-121: 권한/토큰 실패 시 UX 폴백

권한 거부/토큰 실패 시 다음 단계로 진행을 막고 Alert만 노출합니다. 알림 미동의 사용자도 온보딩을 계속할 수 있도록 폴백 UX(건너뛰기/나중에 설정)를 고려해 주세요.


129-129: 하드코딩된 이메일 제거 필요

실서비스에 머지되면 즉시 장애로 이어질 수 있습니다. 온보딩 소스에서 실제 이메일을 주입하거나, 최소한 PR 머지 전에 해당 값이 절대 하드코딩되지 않도록 가드(빌드타임 체크)를 추가하세요.

원하시면 이메일 주입 경로 확정 전까지 임시 가드를 넣는 패치도 제공하겠습니다.


66-71: 민감/준민감 데이터 콘솔 출력 지양

FCM 토큰 콘솔 로그는 제거를 권장합니다. 토큰은 식별자 성격을 가지며 서드파티 확장/공유 디바이스에서 노출될 수 있습니다.

-        console.log('FCM 토큰 발급 성공:', forFcmtoken);

193-193: 비활성화 조건 확인

isDisabled={step === 6}은 현재 플로우에서 도달 불가로 보입니다. 불필요하면 제거하거나 올바른 경계값으로 수정하세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c42048 and f25add8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/client/package.json (1 hunks)
  • apps/client/src/firebase-config.ts (1 hunks)
  • apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (4 hunks)
  • apps/client/src/shared/apis/axios.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (1)
apps/client/src/firebase-config.ts (1)
  • firebaseConfig (1-9)

"@tanstack/react-query": "^5.85.3",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"firebase": "^12.2.1",
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

FCM 동작을 위해 서비스 워커와 환경변수 점검 필요

firebase 의존성 추가만으로는 웹 FCM이 동작하지 않습니다. public/firebase-messaging-sw.js 존재 여부와 VAPID/프로젝트 env 설정을 꼭 확인해 주세요.

다음 스크립트로 빠르게 점검 가능합니다:


🏁 Script executed:

#!/bin/bash
# 1) 서비스 워커 파일 존재 확인
fd -a 'firebase-messaging-sw.*' apps/client || true

# 2) FCM 관련 ENV 키 존재 여부(로컬 env가 레포에 없으면 스킵될 수 있음)
rg -nP 'VITE_FIREBASE_(API_KEY|AUTH_DOMAIN|PROJECT_ID|STORAGE_BUCKET|MESSAGING_SENDER_ID|APP_ID|MEASUREMENT_ID|VAPID_KEY)' apps/client || true

Length of output: 790


public/firebase-messaging-sw.js 없음 — 서비스 워커 추가/등록 필요

  • 검증: FCM 관련 env 참조는 존재함 — apps/client/src/firebase-config.ts (VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN, VITE_FIREBASE_PROJECT_ID, VITE_FIREBASE_STORAGE_BUCKET, VITE_FIREBASE_MESSAGING_SENDER_ID, VITE_FIREBASE_APP_ID, VITE_FIREBASE_MEASUREMENT_ID) 및 apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (VITE_FIREBASE_VAPID_KEY).
  • 조치: public/firebase-messaging-sw.js를 루트(public/)에 추가하고 클라이언트에서 '/firebase-messaging-sw.js'로 서비스워커가 등록되도록 하세요 (navigator.serviceWorker.register('/firebase-messaging-sw.js')).
  • 추가: 배포 환경에 VAPID 키 및 위 ENV 값들이 실제로 설정되어 있는지 확인하세요.
🤖 Prompt for AI Agents
In apps/client/package.json around line 16: the project depends on firebase but
lacks the Firebase Cloud Messaging service worker; add a file
public/firebase-messaging-sw.js at the repository's public root (containing the
FCM service worker setup) and ensure the client registers it via
navigator.serviceWorker.register('/firebase-messaging-sw.js') from app startup
(e.g., in your main client bootstrap or useEffect in a top-level component);
also verify the VAPID key and the VITE_FIREBASE_* env vars are set in deployment
and that the new file is included in the built/public assets so the registration
path resolves in production.

Comment on lines +1 to +9
export const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
}; No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타이핑 및 런타임 검증 추가로 환경오염/미설정 조기 탐지

현재 값이 string | undefined일 수 있어, 빌드/런타임에서 문제를 늦게 발견할 수 있습니다. FirebaseOptions 타이핑과 간단한 런타임 검증을 권장합니다.

+import type { FirebaseOptions } from 'firebase/app';
-export const firebaseConfig = {
+export const firebaseConfig: FirebaseOptions = {
   apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
   authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
   projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
   storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
   messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
   appId: import.meta.env.VITE_FIREBASE_APP_ID,
   measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
 };
+
+// 선택: 필수 키 누락 시 조기 실패
+const missing = Object.entries(firebaseConfig)
+  .filter(([, v]) => !v)
+  .map(([k]) => k);
+if (missing.length) {
+  // eslint-disable-next-line no-console
+  console.error('Missing Firebase env keys:', missing);
+  throw new Error('Firebase env is not configured properly');
+}
📝 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
export const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
};
import type { FirebaseOptions } from 'firebase/app';
export const firebaseConfig: FirebaseOptions = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
};
// 선택: 필수 키 누락 시 조기 실패
const missing = Object.entries(firebaseConfig)
.filter(([, v]) => !v)
.map(([k]) => k);
if (missing.length) {
// eslint-disable-next-line no-console
console.error('Missing Firebase env keys:', missing);
throw new Error('Firebase env is not configured properly');
}
🤖 Prompt for AI Agents
In apps/client/src/firebase-config.ts lines 1-9, the exported config uses raw
import.meta.env values which are typed as string | undefined; update to use the
FirebaseOptions type and add a small runtime validation that each required env
var is present and a string, throwing a clear Error during startup if any are
missing. Construct the firebaseConfig as a properly typed FirebaseOptions object
(import type FirebaseOptions from 'firebase/app' or appropriate module),
validate keys like VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN,
VITE_FIREBASE_PROJECT_ID, VITE_FIREBASE_STORAGE_BUCKET,
VITE_FIREBASE_MESSAGING_SENDER_ID, VITE_FIREBASE_APP_ID (and optional
VITE_FIREBASE_MEASUREMENT_ID) before returning, and provide a helpful error
message listing any missing variables so misconfiguration is detected early.

Comment on lines +13 to +16
import { firebaseConfig } from '../../../../firebase-config';
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken } from 'firebase/messaging';

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Firebase import 정비 및 상대경로 개선

  • initializeApp만 가져오면 렌더마다 재초기화 리스크가 있습니다. getApps/getApp을 함께 임포트해 단일 인스턴스를 보장하세요.
  • isSupported 도입으로 비지원 브라우저 가드 필요.
  • firebase-config 상대경로는 취약합니다. 기존 alias(@pages, @shared)와 동일하게 alias를 부여해 주세요.
-import { firebaseConfig } from '../../../../firebase-config'; 
-import { initializeApp } from 'firebase/app';
-import { getMessaging, getToken } from 'firebase/messaging';
+import { firebaseConfig } from '@/firebase-config'; // 또는 적절한 alias
+import { initializeApp, getApps, getApp } from 'firebase/app';
+import { getMessaging, getToken, isSupported } from 'firebase/messaging';

추가로, 상단 react 임포트에 useMemo가 필요해질 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
13 to 16, replace the current firebase imports and relative config path: import
getApps/getApp alongside initializeApp and use a single-instance pattern (if
getApps().length === 0 then initializeApp(config) else getApp()), import
isSupported from 'firebase/messaging' and guard messaging code with await
isSupported() before calling getMessaging/getToken, change the
'../../../../firebase-config' relative import to the project alias (e.g.
'@shared/firebase-config' or the correct alias used in the repo), and add
useMemo to the React import at the top since it may be needed for memoized
initialization; update code paths accordingly to avoid reinitialization and to
skip messaging on unsupported browsers.

Comment on lines +51 to +53
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복 초기화 버그: 렌더마다 initializeApp 호출

컴포넌트 본문에서 initializeApp을 호출하면 재렌더 시 [DEFAULT] already exists 예외가 발생할 수 있습니다. 단일 인스턴스를 보장하도록 변경하세요.

-  const app = initializeApp(firebaseConfig);
-  const messaging = getMessaging(app);
+  const app = (getApps().length ? getApp() : initializeApp(firebaseConfig));
+  const messaging = getMessaging(app);

추가로 안정성을 위해 모듈 스코프로 올리는 것도 고려해 주세요:

// (컴포넌트 밖 상단)
const app = (getApps().length ? getApp() : initializeApp(firebaseConfig));
const messaging = getMessaging(app);
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
51-53, the code calls initializeApp inside the component causing "[DEFAULT]
already exists" on re-render; change it to ensure a single Firebase app instance
(use getApps().length ? getApp() : initializeApp(firebaseConfig) or try getApp()
with fallback to initializeApp) and obtain messaging from that instance, and
move these calls to module scope (top of the file, outside the component) so
they run once per module load.

Comment on lines +54 to +78
const requestFCMToken = async (): Promise<string | null> => {
try {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
alert('알림 권한 허용이 필요합니다!');
return null;
}

const forFcmtoken = await getToken(messaging, {
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
});

if (forFcmtoken) {
console.log('FCM 토큰 발급 성공:', forFcmtoken);
return forFcmtoken;
} else {
alert('토큰 생성 실패. 다시 시도해주세요.');
return null;
}
} catch (error) {
console.error('FCM 토큰 받는 도중 오류:', error);
alert('알림 설정 중 오류가 발생했습니다. 다시 시도해주세요.');
return null;
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

웹 FCM 필수 가드 누락: 브라우저 지원 체크 + 서비스워커 미등록 상태에서 getToken 호출

  • isSupported() 체크 없이 호출 시 일부 브라우저/환경에서 런타임 에러.
  • firebase-messaging-sw.js를 등록하지 않으면 토큰 발급/백그라운드 수신 실패가 잦습니다. getTokenserviceWorkerRegistration을 전달하세요.
   const requestFCMToken = async (): Promise<string | null> => {
     try {
+      // 0) 브라우저 지원 확인
+      if (!(await isSupported())) {
+        alert('현재 브라우저는 푸시 알림을 지원하지 않습니다.');
+        return null;
+      }
+
+      // 1) 서비스워커 등록
+      const swReg = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
+
       const permission = await Notification.requestPermission();
       if (permission !== 'granted') {
         alert('알림 권한 허용이 필요합니다!');
         return null;
       }
 
-      const forFcmtoken = await getToken(messaging, {
-        vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
-      });
+      const newToken = await getToken(messaging, {
+        vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
+        serviceWorkerRegistration: swReg,
+      });
 
-      if (forFcmtoken) {
-        console.log('FCM 토큰 발급 성공:', forFcmtoken);
-        return forFcmtoken;
+      if (newToken) {
+        // console.log는 민감정보 노출 우려. 필요 시 debug 플래그 뒤에서만 출력하세요.
+        return newToken;
       } else {
         alert('토큰 생성 실패. 다시 시도해주세요.');
         return null;
       }

운영 팁:

  • apps/client/public/firebase-messaging-sw.js를 추가하고 배포 루트(/{scope})와 경로가 일치하도록 호스팅 설정을 확인하세요.
  • 로컬 개발은 https:// 혹은 http://localhost에서만 동작합니다.
📝 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 requestFCMToken = async (): Promise<string | null> => {
try {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
alert('알림 권한 허용이 필요합니다!');
return null;
}
const forFcmtoken = await getToken(messaging, {
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
});
if (forFcmtoken) {
console.log('FCM 토큰 발급 성공:', forFcmtoken);
return forFcmtoken;
} else {
alert('토큰 생성 실패. 다시 시도해주세요.');
return null;
}
} catch (error) {
console.error('FCM 토큰 받는 도중 오류:', error);
alert('알림 설정 중 오류가 발생했습니다. 다시 시도해주세요.');
return null;
}
};
const requestFCMToken = async (): Promise<string | null> => {
try {
// 0) 브라우저 지원 확인
if (!(await isSupported())) {
alert('현재 브라우저는 푸시 알림을 지원하지 않습니다.');
return null;
}
// 1) 서비스워커 등록
const swReg = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
alert('알림 권한 허용이 필요합니다!');
return null;
}
const newToken = await getToken(messaging, {
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
serviceWorkerRegistration: swReg,
});
if (newToken) {
// console.log는 민감정보 노출 우려. 필요 시 debug 플래그 뒤에서만 출력하세요.
return newToken;
} else {
alert('토큰 생성 실패. 다시 시도해주세요.');
return null;
}
} catch (error) {
console.error('FCM 토큰 받는 도중 오류:', error);
alert('알림 설정 중 오류가 발생했습니다. 다시 시도해주세요.');
return null;
}
};
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
54 to 78, the FCM token flow calls getToken without checking web messaging
support and without supplying a serviceWorkerRegistration; add an isSupported()
guard before requesting permission and early-return if unsupported, attempt to
register or locate the firebase messaging service worker
(public/firebase-messaging-sw.js) and pass the obtained
serviceWorkerRegistration into getToken via the serviceWorkerRegistration
option, and handle the case where service worker registration fails by showing
the existing alert/console error and returning null.

Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

FCM 토큰 관련 로직이 이전보다 훨씬 간소화되고 깔끔해졌네요!
message 관련된 로직은 추가가 되어야 하는거죠??

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/client/src/shared/apis/axios.ts (1)

25-27: acorns 요청: 중복 쿼리스트링 버그 수정 필요

경로에 ?now=를 하드코딩한 상태에서 params: { now }를 추가해 동일 파라미터가 중복됩니다. 서버에 따라 예기치 않은 값이 전달될 수 있습니다. 아래처럼 URL의 쿼리 문자열을 제거하세요.

-  const { data } = await apiRequest.get('/api/v1/users/acorns?now=', {
-    params: { now },
-  });
+  const { data } = await apiRequest.get('/api/v1/users/acorns', {
+    params: { now },
+  });
🧹 Nitpick comments (3)
apps/client/src/shared/apis/axios.ts (3)

31-31: 타입명 PascalCase로 정렬

코드베이스 컨벤션에 맞춰 인터페이스명을 PascalCase로 맞추면 가독성이 좋아집니다. 사용처 타입도 함께 변경하세요.

-export interface postSignUpRequest {
+export interface PostSignUpRequest {
   ...
 }
 
-export const postSignUp = async (responsedata: postSignUpRequest) => {
+export const postSignUp = async (responsedata: PostSignUpRequest) => {

Also applies to: 37-37


9-14: API 반환값 형식 통일

일부 함수는 response 전체를, 일부는 data 혹은 data.data를 반환합니다. 호출부 혼란과 버그의 원인이 되므로 일관된 계약으로 정리하세요(예: 항상 response.data 반환).

예시 수정:

-export const postCategory = async (categoryName: string) => {
-  const response = await apiRequest.post('/api/v1/categories', {
-    categoryName,
-  });
-  return response;
-};
+export const postCategory = async (categoryName: string) => {
+  const { data } = await apiRequest.post('/api/v1/categories', { categoryName });
+  return data;
+};

-export const deleteCategory = async (id: number) => {
-  const response = await apiRequest.delete(`/api/v1/categories/${id}`);
-  return response;
-};
+export const deleteCategory = async (id: number) => {
+  const { data } = await apiRequest.delete(`/api/v1/categories/${id}`);
+  return data;
+};

또한 data.data를 기대하는 엔드포인트는 서버 응답 스키마와 합의해 한 단계로 평탄화하는 것도 고려해 주세요.

Also applies to: 16-21, 37-40, 49-52


33-33: remindDefault 타입을 리터럴 유니온으로 한정

가능한 값이 정해져 있다면 string 대신 리터럴 유니온(예: 'immediate' | 'daily' | 'weekly')으로 좁혀 타입 안전성을 높이세요. 백엔드 스키마(예: OpenAPI/DTO)와 허용값을 먼저 확인 바랍니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f25add8 and 90f10c6.

📒 Files selected for processing (1)
  • apps/client/src/shared/apis/axios.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook

Comment on lines 31 to 35
export interface postSignUpRequest {
email: string;
remindDefault: string;
fcmToken: string;
email: string,
remindDefault: string,
fcmToken: string | null
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

fcmToken: null 전송 대신 “필드 생략(옵셔널)” 권장 — 백엔드 계약 확인 필요

string | null로 두면 실제 전송 바디에 "fcmToken": null이 포함됩니다. 백엔드 DTO가 null을 허용하지 않으면 400이 날 수 있습니다. 토큰이 없을 때는 필드를 아예 생략하는 쪽이 안전합니다.

-export interface postSignUpRequest {
+export interface postSignUpRequest {
   email: string,
   remindDefault: string,
-  fcmToken: string | null
+  fcmToken?: string
 }

추가 권장: 실제 전송 직전에 undefined인 필드는 제거되도록 직렬화(예: 스프레드로 조건부 포함)되어 있는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo

echo "=== Show file apps/client/src/shared/apis/axios.ts (if exists) ==="
if [ -f apps/client/src/shared/apis/axios.ts ]; then
  sed -n '1,240p' apps/client/src/shared/apis/axios.ts || true
else
  echo "NOT FOUND: apps/client/src/shared/apis/axios.ts"
fi

echo
echo "=== rg: postSignUpRequest ==="
rg -n --hidden -S "postSignUpRequest" -g '!.git' -g '!**/node_modules/**' -C3 || true

echo
echo "=== rg: fcmToken ==="
rg -n --hidden -S "fcmToken" -g '!.git' -g '!**/node_modules/**' -C3 || true

echo
echo "=== rg: signup/register endpoints / function names ==="
rg -n --hidden -S "signup|signUp|register/step|registerUser|register" -g '!.git' -g '!**/node_modules/**' -C3 || true

echo
echo "=== rg: axios.post / axios.create usage ==="
rg -n --hidden -S "axios\.post|axios\.create|axios\(" -g '!.git' -g '!**/node_modules/**' -C3 || true

echo
echo "=== rg: JSON.stringify / transformRequest / replacer ==="
rg -n --hidden -S "JSON.stringify|transformRequest|replacer|serialize" -g '!.git' -g '!**/node_modules/**' -C3 || true

echo
echo "=== rg: possible backend DTOs mentioning fcmToken or signup DTOs ==="
rg -n --hidden -S "fcmToken|FCM|SignUpDto|SignUpDTO|SignupRequest|SignUpRequest|SignUpDto" -g '!.git' -g '!**/node_modules/**' -C3 || true

Length of output: 2722


추가 확인을 위해 아래 스크립트를 실행하겠습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo

echo "=== apps/client/src/shared/apis/axios.ts (existing) ==="
if [ -f apps/client/src/shared/apis/axios.ts ]; then
  sed -n '1,240p' apps/client/src/shared/apis/axios.ts || true
else
  echo "NOT FOUND: apps/client/src/shared/apis/axios.ts"
fi

echo
echo "=== Find any axiosInstance files ==="
find . -type f \( -iname "*axiosinstance*.ts" -o -iname "*axiosinstance*.js" -o -iname "*axiosinstance*.tsx" -o -iname "*axiosinstance*.jsx" \) -print || true

echo
echo "=== Show first axiosInstance file content (if found) ==="
file=$(find . -type f \( -iname "*axiosinstance*.ts" -o -iname "*axiosinstance*.js" -o -iname "*axiosinstance*.tsx" -o -iname "*axiosinstance*.jsx" \) | head -n1 || true)
if [ -n "$file" ]; then
  echo "FOUND: $file"
  sed -n '1,240p' "$file" || true
else
  echo "No axiosInstance file found"
fi

echo
echo "=== Search for postSignUp call sites ==="
rg -n --hidden -S "postSignUp\\(" || true

echo
echo "=== Search for direct '/api/v1/auth/signup' usages ==="
rg -n --hidden -S "/api/v1/auth/signup" || true

echo
echo "=== Search for fcmToken usages across repo ==="
rg -n --hidden -S "fcmToken" || true

echo
echo "=== Search for where signup payload / types are referenced ==="
rg -n --hidden -S "postSignUpRequest|responsedata" || true

echo
echo "=== Show package.json files (root and apps/client) ==="
if [ -f package.json ]; then
  echo "--- root package.json ---"
  sed -n '1,240p' package.json || true
fi
if [ -f apps/client/package.json ]; then
  echo "--- apps/client/package.json ---"
  sed -n '1,240p' apps/client/package.json || true
fi

Length of output: 8589


fcmToken을 null로 전송하지 말고 필드를 생략하세요 — 백엔드 DTO(가입 API) 허용 여부 확인 필요

현재 postSignUp에 전달되는 객체가 fcmToken === null이면 요청 바디에 "fcmToken": null이 포함됩니다. 백엔드가 null을 허용하지 않으면 400을 일으킬 수 있으므로 인터페이스를 옵셔널로 변경하고 전송 시 null인 경우 필드를 제외하세요.

위치: apps/client/src/shared/apis/axios.ts (postSignUpRequest), apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (fcmToken 상태 및 전송)

-export interface postSignUpRequest {
+export interface postSignUpRequest {
   email: string,
   remindDefault: string,
-  fcmToken: string | null
+  fcmToken?: string
 }

전송 구현 힌트: payload 생성 시 ...(fcmToken ? { fcmToken } : {}) 처럼 조건부 포함하거나 fcmToken을 undefined로 두고 JSON.stringify가 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
export interface postSignUpRequest {
email: string;
remindDefault: string;
fcmToken: string;
email: string,
remindDefault: string,
fcmToken: string | null
}
export interface postSignUpRequest {
email: string,
remindDefault: string,
fcmToken?: string
}

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

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 온보딩 FCM 연동

2 participants