ec6bf2922f
핵심 수정 (사용자 보고: 알림 탭 → 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>
116 lines
4.8 KiB
TypeScript
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;
|
|
}
|