Files
distribution_erp/src/components/native-push-auto-register.tsx
T
chpark ec6bf2922f fix(push): 알림 탭 시 앱으로 열림 (Chrome 아닌) + Tiptap 리치 에디터 추가
핵심 수정 (사용자 보고: 알림 탭 → Chrome 으로 열림):
- src/lib/firebase-push.ts: android.notification.click_action 제거
  → OS 가 URL 로 해석해 브라우저 인텐트 (ACTION_VIEW) 로 처리하던 문제
  → 빈 click_action 으로 두면 LAUNCHER(MainActivity) 가 자동 진입 → 앱이 열림
- src/components/native-push-auto-register.tsx:
  + pushNotificationActionPerformed 리스너 — 알림 탭 시 data.url 로 webview 이동
  + pushNotificationReceived 리스너 — 포그라운드 진단 로그
  → 알림 누르면 모모유통 ERP 앱에서 해당 페이지(공지/주문 등) 자동 표시

신규 (사용자 요청: 본문 HTML 처럼 + 이미지 복붙):
- src/components/rich-editor.tsx — Tiptap 기반 위지위그 에디터
  + 클립보드 이미지 paste → 자동 업로드 → img 태그 삽입
  + 드래그앤드롭 이미지 동일 처리
  + 툴바: 제목/볼드/이탤릭/취소선/리스트/인용/링크/이미지/실행취소
  + 출력: HTML 문자열 (공지 페이지에서 dangerouslySetInnerHTML 렌더)
- src/app/(main)/m/admin/notices/page.tsx 에 RichEditor import 추가
  (실제 textarea → RichEditor 교체는 다음 커밋에서)

운영 운영 정리:
- /home/chpark/momo-erp/firebase-sa.json chmod 644 → 컨테이너 node 사용자 읽기 OK
- momo125 의 옛 Chrome web-push 구독 DB 정리 (fcm 만 유지)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 00:30:04 +09:00

116 lines
4.8 KiB
TypeScript

"use client";
// Capacitor 네이티브 앱 자동 푸시 등록 — UI 없음, 백그라운드 동작.
// 로그인 후 첫 페이지 로드 시 자동으로:
// 1. 안드로이드 시스템 알림 권한 prompt (POST_NOTIFICATIONS)
// 2. 권한 허용되면 FCM 토큰 발급
// 3. 백엔드 /api/m/push/fcm-token 에 등록
// 4. 새 상품/공지 알림이 자동 도착 시작
//
// 일반 웹 브라우저(Capacitor 아님)에서는 아무것도 안 함 — 회원정보의 PushOptIn 카드 사용.
import { useEffect, useRef } from "react";
// Capacitor 글로벌 타입은 push-optin.tsx 의 declare global 과 동일 — 한 쪽만 선언해도 충분.
// 여기서는 import 한 PushOptIn 없으므로 별도 type 만 정의.
interface PN {
requestPermissions: () => Promise<{ receive: "granted" | "denied" | "prompt" }>;
register: () => Promise<void>;
addListener: (event: string, cb: (data: PNData) => void) => Promise<{ remove: () => void }>;
}
interface PNData {
value?: string;
error?: string;
notification?: { title?: string; body?: string; data?: Record<string, string> };
actionId?: string;
}
function getCap(): { isNativePlatform?: () => boolean; Plugins?: { PushNotifications?: PN } } | undefined {
if (typeof window === "undefined") return undefined;
return (window as unknown as { Capacitor?: { isNativePlatform?: () => boolean; Plugins?: { PushNotifications?: PN } } }).Capacitor;
}
const SESSION_KEY = "momo-native-push-tried";
export function NativePushAutoRegister() {
const ranRef = useRef(false);
useEffect(() => {
if (ranRef.current) return;
ranRef.current = true;
const cap = getCap();
const PN = cap?.Plugins?.PushNotifications;
if (!cap?.isNativePlatform?.() || !PN) return;
// 같은 세션 내 중복 호출 방지 (페이지 이동 마다 다시 묻지 않음)
try { if (sessionStorage.getItem(SESSION_KEY)) return; } catch { /* ignore */ }
try { sessionStorage.setItem(SESSION_KEY, "1"); } catch { /* ignore */ }
// 알림 탭 시 (앱 백그라운드/종료 → 사용자가 알림 누름) → webview 에서 해당 URL 로 이동.
// FCM payload 의 data.url 사용. 절대경로 또는 상대경로 둘 다 허용.
PN.addListener("pushNotificationActionPerformed", (d) => {
const url = d.notification?.data?.url;
if (!url) return;
try {
if (/^https?:\/\//i.test(url)) {
window.location.href = url;
} else {
window.location.assign(url);
}
} catch (err) { console.error("[native-push] navigate 실패:", err); }
}).catch(() => {});
// 포그라운드 알림 도착 — 콘솔 로그만 (Capacitor 가 자동으로 알림 표시 안 함, OS 알림으로 옴)
PN.addListener("pushNotificationReceived", (d) => {
console.log("[native-push] foreground:", d.notification?.title);
}).catch(() => {});
(async () => {
try {
// 1) 권한 요청 (이미 granted 면 즉시 granted 반환)
const perm = await PN.requestPermissions();
if (perm.receive !== "granted") {
console.log("[native-push] 권한 거부:", perm.receive);
return;
}
// 2) FCM 토큰 받기 (10초 안에)
const token = await new Promise<string | null>((resolve) => {
let resolved = false;
const timeout = setTimeout(() => { if (!resolved) { resolved = true; resolve(null); } }, 10000);
PN.addListener("registration", (d) => {
if (resolved) return; resolved = true; clearTimeout(timeout);
resolve(d.value || null);
});
PN.addListener("registrationError", (d) => {
if (resolved) return; resolved = true; clearTimeout(timeout);
console.error("[native-push] registrationError:", d);
resolve(null);
});
PN.register();
});
if (!token) {
console.warn("[native-push] FCM 토큰 발급 실패 — 다음 로그인에서 재시도");
return;
}
// 3) 백엔드 등록 — 401 이면 로그인 안 됐다는 뜻, 다음 로그인 시 재시도
const r = await fetch("/api/m/push/fcm-token", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, userAgent: navigator.userAgent }),
});
if (r.ok) {
console.log("[native-push] ✔ FCM 토큰 등록 완료");
} else if (r.status === 401) {
// 로그인 전이면 세션 키 제거 → 로그인 후 다시 시도되도록
try { sessionStorage.removeItem(SESSION_KEY); } catch { /* ignore */ }
} else {
console.warn(`[native-push] 백엔드 등록 실패 (HTTP ${r.status})`);
}
} catch (err) {
console.error("[native-push] 자동 등록 예외:", err);
}
})();
}, []);
return null;
}