feat: PWA FCM 클라이언트 실통합 + 어댑터 키 주입 런처
- firebase SDK 통합: initPush getToken 활성화(동적 import) + isSupported 가드 - public/firebase-messaging-sw.js 백그라운드 수신 SW(웹 config 쿼리스트링 전달) - run-api-with-adapters.sh: adapter.env.local 로드 후 ga-api bootRun - 키 파일(adapter.env.local / .env.local)은 git-ignored 템플릿으로 별도 제공 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+1037
-4
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
|||||||
"antd-mobile-icons": "^0.3.0",
|
"antd-mobile-icons": "^0.3.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"firebase": "^12.14.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/* FCM 백그라운드 메시지 수신 서비스워커 (vite-plugin-pwa SW 와 별개).
|
||||||
|
* Firebase 웹 config 는 정적 파일에 박지 않고, initPush 등록 시 쿼리스트링으로 전달받는다. */
|
||||||
|
importScripts('https://www.gstatic.com/firebasejs/12.14.0/firebase-app-compat.js');
|
||||||
|
importScripts('https://www.gstatic.com/firebasejs/12.14.0/firebase-messaging-compat.js');
|
||||||
|
|
||||||
|
const p = new URL(self.location).searchParams;
|
||||||
|
firebase.initializeApp({
|
||||||
|
apiKey: p.get('apiKey'),
|
||||||
|
projectId: p.get('projectId'),
|
||||||
|
appId: p.get('appId'),
|
||||||
|
messagingSenderId: p.get('messagingSenderId'),
|
||||||
|
});
|
||||||
|
|
||||||
|
firebase.messaging().onBackgroundMessage((payload) => {
|
||||||
|
const n = payload.notification || {};
|
||||||
|
self.registration.showNotification(n.title || '알림', {
|
||||||
|
body: n.body || '',
|
||||||
|
icon: '/pwa-192x192.png',
|
||||||
|
data: payload.data || {},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,11 +6,10 @@ import { pushApi } from '@/api/push';
|
|||||||
* 활성 조건: 빌드 환경변수 `VITE_FCM_*` 가 모두 설정된 경우에만 동작한다.
|
* 활성 조건: 빌드 환경변수 `VITE_FCM_*` 가 모두 설정된 경우에만 동작한다.
|
||||||
* 미설정 시 no-op (개발/미연동 환경에서 안전).
|
* 미설정 시 no-op (개발/미연동 환경에서 안전).
|
||||||
*
|
*
|
||||||
* <b>운영 활성화 절차</b>:
|
* firebase SDK 통합 완료 — 활성 조건은 `.env(.local)` 의 VITE_FCM_* 주입뿐이다.
|
||||||
* 1) Firebase 프로젝트 생성 → 웹 앱 등록 → 설정값을 .env 의 VITE_FCM_* 로 주입
|
* 1) Firebase 콘솔 → 웹 앱 등록 → 설정값을 .env.local 의 VITE_FCM_* 로 주입
|
||||||
* 2) `npm i firebase` 후 아래 TODO 블록의 firebase/messaging 초기화 활성화
|
* 2) getToken(VAPID key)으로 FCM 토큰 발급 → pushApi.register(token) 자동 호출
|
||||||
* 3) getToken(VAPID key)으로 FCM 토큰 발급 → pushApi.register(token) 호출
|
* 3) public/firebase-messaging-sw.js 가 백그라운드 수신 처리
|
||||||
* 4) public/firebase-messaging-sw.js 서비스워커 배치
|
|
||||||
*
|
*
|
||||||
* 서버측(PushAdapter=fcm + FCM_PROJECT_ID/FCM_ACCESS_TOKEN)과 함께 활성화해야 실제 발송된다.
|
* 서버측(PushAdapter=fcm + FCM_PROJECT_ID/FCM_ACCESS_TOKEN)과 함께 활성화해야 실제 발송된다.
|
||||||
*/
|
*/
|
||||||
@@ -38,14 +37,38 @@ export async function initPush(): Promise<void> {
|
|||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
if (permission !== 'granted') return;
|
if (permission !== 'granted') return;
|
||||||
|
|
||||||
// TODO(운영): `npm i firebase` 후 활성화.
|
const { isSupported, getMessaging, getToken } = await import('firebase/messaging');
|
||||||
// const { initializeApp } = await import('firebase/app');
|
if (!(await isSupported())) {
|
||||||
// const { getMessaging, getToken } = await import('firebase/messaging');
|
if (import.meta.env.DEV) console.info('[push] 이 브라우저는 FCM 미지원 — skip.');
|
||||||
// const appFb = initializeApp(cfg);
|
return;
|
||||||
// const messaging = getMessaging(appFb);
|
}
|
||||||
// const token = await getToken(messaging, { vapidKey: cfg.vapidKey });
|
|
||||||
// if (token) await pushApi.register(token, 'WEB');
|
const { initializeApp, getApps } = await import('firebase/app');
|
||||||
void pushApi; // 등록 API 는 토큰 발급 후 호출 (위 TODO 활성화 시)
|
const appFb = getApps().length
|
||||||
|
? getApps()[0]
|
||||||
|
: initializeApp({
|
||||||
|
apiKey: cfg.apiKey,
|
||||||
|
projectId: cfg.projectId,
|
||||||
|
appId: cfg.appId,
|
||||||
|
messagingSenderId: cfg.messagingSenderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// FCM 백그라운드 수신용 서비스워커 (vite-plugin-pwa SW 와 별개).
|
||||||
|
// 웹 config 는 정적 SW 파일에 박지 않고 쿼리스트링으로 전달.
|
||||||
|
const swQuery = new URLSearchParams({
|
||||||
|
apiKey: cfg.apiKey,
|
||||||
|
projectId: cfg.projectId,
|
||||||
|
appId: cfg.appId,
|
||||||
|
messagingSenderId: cfg.messagingSenderId ?? '',
|
||||||
|
}).toString();
|
||||||
|
const swReg = await navigator.serviceWorker.register(`/firebase-messaging-sw.js?${swQuery}`);
|
||||||
|
|
||||||
|
const messaging = getMessaging(appFb);
|
||||||
|
const token = await getToken(messaging, {
|
||||||
|
vapidKey: cfg.vapidKey,
|
||||||
|
serviceWorkerRegistration: swReg,
|
||||||
|
});
|
||||||
|
if (token) await pushApi.register(token, 'WEB');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (import.meta.env.DEV) console.warn('[push] init 실패', e);
|
if (import.meta.env.DEV) console.warn('[push] init 실패', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 외부연동 어댑터 키를 주입하여 ga-api 를 기동한다.
|
||||||
|
# adapter.env.local 의 값을 환경변수로 로드한 뒤 bootRun.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [[ ! -f adapter.env.local ]]; then
|
||||||
|
echo "adapter.env.local 이 없습니다. 키를 먼저 채우세요." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
set -a; source adapter.env.local; set +a
|
||||||
|
|
||||||
|
echo "[adapters] bank=$ADAPTER_BANK message=$ADAPTER_MESSAGE push=$ADAPTER_PUSH"
|
||||||
|
./gradlew :ga-api:bootRun --args='--spring.profiles.active=trading_ai'
|
||||||
Reference in New Issue
Block a user