네이티브 앱이 받은 푸시 알림을 WebView에서
web-bridge.js로 간단하게 처리하세요
네이티브 앱과 WebView 간의 푸시 알림 흐름
web-bridge.js만 사용하면 끝입니다.
네이티브 앱이 이미 FCM을 구현했다면 바로 사용 가능
Firebase 설정, FCM 토큰 등 모두 Nativify가 처리
OS 레벨 푸시 알림의 빠른 성능과 배터리 효율
단 몇 줄의 코드로 푸시 수신 및 처리
웹 개발자 관점에서 푸시 받기
네이티브 앱 개발자가 제공한 web-bridge.js 파일을 HTML에 추가합니다.
<!-- HTML에 스크립트 추가 -->
<script src="./web-bridge.js"></script>
<!-- 또는 NPM 사용 -->
<script>
import WebBridge from './web-bridge.js';
</script>
WebBridge 인스턴스를 생성합니다. FCM 설정은 필요 없습니다!
// JavaScript
const bridge = new WebBridge({ debug: true });
// 네이티브 연결 확인
if (bridge.isAvailable()) {
console.log('✅ 네이티브 앱 연결됨');
} else {
console.log('⚠️ 네이티브 앱이 아님 (브라우저 모드)');
}
또는 Vue/React에서:
// Vue 3 Composition API
import { ref, onMounted } from 'vue';
const bridge = ref(null);
onMounted(() => {
bridge.value = new WebBridge();
});
// React
import { useEffect, useState } from 'react';
function App() {
const [bridge, setBridge] = useState(null);
useEffect(() => {
setBridge(new WebBridge());
}, []);
}
네이티브 앱이 푸시를 받으면 WebView로 자동 전달됩니다. 리스너만 등록하면 끝!
// 푸시 메시지 수신
bridge.on('response', (data) => {
if (data.type === 'PUSH_NOTIFICATION') {
console.log('📬 푸시 수신:', data);
// 알림 정보
const title = data.title;
const body = data.body;
const customData = data.data; // 추가 데이터
// UI 업데이트
showNotificationBanner(title, body);
// 배지 업데이트
updateBadgeCount(data.badge);
// 사운드 재생 (선택사항)
playNotificationSound();
}
});
// 알림 배너 표시 함수
function showNotificationBanner(title, body) {
// 화면 상단에 배너 표시
const banner = document.createElement('div');
banner.className = 'notification-banner';
banner.innerHTML = `
<strong>${title}</strong>
<p>${body}</p>
`;
document.body.appendChild(banner);
// 3초 후 자동 제거
setTimeout(() => banner.remove(), 3000);
}
푸시 메시지에 포함된 커스텀 데이터로 다양한 동작을 수행할 수 있습니다.
bridge.on('response', (data) => {
if (data.type === 'PUSH_NOTIFICATION') {
const customData = data.data;
// 푸시 타입에 따라 다른 동작
switch (customData.type) {
case 'NEW_MESSAGE':
// 메시지 페이지로 이동
router.push(`/messages/${customData.messageId}`);
break;
case 'NEW_ORDER':
// 주문 상세 페이지로 이동
router.push(`/apps/${customData.appId}`);
break;
case 'PROMOTION':
// 프로모션 모달 표시
showPromotionModal(customData.promotionId);
break;
default:
// 기본 알림 배너 표시
showNotificationBanner(data.title, data.body);
}
}
});
사용자에게 알림 권한을 요청할 수 있습니다 (네이티브 브릿지를 통해).
// 네이티브 앱에 권한 요청
async function requestNotificationPermission() {
try {
const response = await bridge._request('REQUEST_NOTIFICATION_PERMISSION');
console.log('알림 권한:', response);
} catch (error) {
console.error('권한 요청 실패:', error);
}
}
WebView에서 네이티브 브릿지를 통해 로컬 알림을 표시할 수 있습니다.
// 네이티브 브릿지를 통해 로컬 알림 표시
async function showLocalNotification(title, body) {
try {
await bridge.showNotification(title, body);
console.log('✅ 알림 표시 완료');
} catch (error) {
console.error('❌ 알림 표시 실패:', error);
}
}
// 사용 예시
showLocalNotification('배달 완료', '주문하신 음식이 도착했습니다!');
Vanilla JS, Vue, React, Angular, Svelte에서 사용하기
// main.js
// 알림 관리 클래스
class NotificationManager {
constructor() {
this.bridge = null; // WebBridge 인스턴스
this.notifications = []; // 받은 알림 목록
}
// WebBridge 초기화
init() {
this.bridge = new WebBridge();
// 네이티브 앱으로부터 메시지 수신 리스너 등록
this.bridge.on('response', this.handlePush.bind(this));
this.render();
}
// 푸시 메시지 처리
handlePush(data) {
if (data.type === 'PUSH_NOTIFICATION') {
// 새 알림을 목록 맨 앞에 추가
this.notifications.unshift({
id: Date.now(),
title: data.title,
body: data.body,
data: data.data,
});
this.render(); // UI 업데이트
}
}
// UI 렌더링
render() {
const container = document.getElementById('notifications');
// 알림 목록을 HTML로 변환
container.innerHTML = this.notifications.map(notif => `
<div class="notification-item">
<h3>${notif.title}</h3>
<p>${notif.body}</p>
</div>
`).join('');
}
}
// 앱 초기화
const manager = new NotificationManager();
manager.init();
// useNotification.js (Composable)
import { ref, onMounted, onUnmounted } from 'vue';
// 알림 관리 Composable
export function useNotification() {
const bridge = ref(null); // WebBridge 인스턴스
const notifications = ref([]); // 알림 목록 (반응형)
// 푸시 메시지 처리 함수
const handlePush = (data) => {
if (data.type === 'PUSH_NOTIFICATION') {
// 새 알림을 목록 맨 앞에 추가
notifications.value.unshift({
id: Date.now(),
title: data.title,
body: data.body,
data: data.data,
timestamp: new Date(),
});
}
};
// 컴포넌트 마운트 시 WebBridge 초기화
onMounted(() => {
bridge.value = new WebBridge();
bridge.value.on('response', handlePush); // 리스너 등록
});
// 컴포넌트 언마운트 시 리스너 정리
onUnmounted(() => {
if (bridge.value) {
bridge.value.off('response', handlePush);
}
});
return { notifications }; // 알림 목록 반환
}
// App.vue
<script setup>
import { useNotification } from './composables/useNotification';
// Composable 사용
const { notifications } = useNotification();
</script>
<template>
<!-- 알림 목록 표시 -->
<div v-for="notif in notifications" :key="notif.id">
<h3>{{ notif.title }}</h3>
<p>{{ notif.body }}</p>
</div>
</template>
// useNotification.js (Hook)
import { useState, useEffect } from 'react';
// 알림 관리 Custom Hook
export function useNotification() {
const [bridge, setBridge] = useState(null); // WebBridge 인스턴스
const [notifications, setNotifications] = useState([]); // 알림 목록
// 컴포넌트 마운트 시 실행
useEffect(() => {
const WebBridge = new WebBridge();
setBridge(WebBridge);
// 푸시 메시지 처리 함수
const handlePush = (data) => {
if (data.type === 'PUSH_NOTIFICATION') {
// 상태 업데이트: 새 알림을 목록 맨 앞에 추가
setNotifications(prev => [{
id: Date.now(),
title: data.title,
body: data.body,
data: data.data,
}, ...prev]);
}
};
// 네이티브 메시지 리스너 등록
WebBridge.on('response', handlePush);
// 컴포넌트 언마운트 시 리스너 정리
return () => {
WebBridge.off('response', handlePush);
};
}, []); // 빈 의존성 배열: 한 번만 실행
return { notifications }; // 알림 목록 반환
}
// App.jsx
function App() {
const { notifications } = useNotification(); // Hook 사용
return (
<div>
{notifications.map(notif => (
<div key={notif.id}>
<h3>{notif.title}</h3>
<p>{notif.body}</p>
</div>
))}
</div>
);
}
// notification.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
// \uc54c\ub9bc \uad00\ub9ac \uc11c\ube44\uc2a4 (Singleton)
@Injectable({ providedIn: 'root' })
export class NotificationService {
private bridge: any;
// RxJS Subject\ub85c \uc54c\ub9bc \ubaa9\ub85d \uad00\ub9ac
private notificationsSubject = new BehaviorSubject<any[]>([]);
public notifications$ = this.notificationsSubject.asObservable();
// WebBridge \ucd08\uae30\ud654
init() {
this.bridge = new (window as any).WebBridge();
// 네이티브 메시지 리스너 등록
this.bridge.on('response', (data: any) => {
if (data.type === 'PUSH_NOTIFICATION') {
const current = this.notificationsSubject.value;
// \uc0c8 \uc54c\ub9bc\uc744 \ubaa9\ub85d \ub9e8 \uc55e\uc5d0 \ucd94\uac00
this.notificationsSubject.next([{
id: Date.now(),
title: data.title,
body: data.body,
}, ...current]);
}
});
}
}
// app.component.ts
@Component({
selector: 'app-root',
template: `
<!-- \uc54c\ub9bc \ubaa9\ub85d \ud45c\uc2dc (async pipe \uc0ac\uc6a9) -->
<div *ngFor="let notif of notifications$ | async">
<h3>{{ notif.title }}</h3>
<p>{{ notif.body }}</p>
</div>
`
})
export class AppComponent implements OnInit {
notifications$ = this.notificationService.notifications$;
constructor(private notificationService: NotificationService) {}
ngOnInit() { this.notificationService.init(); } // \ucd08\uae30\ud654
}
// App.svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
// 알림 목록 저장소 (반응형)
const notifications = writable([]);
let bridge;
// 푸시 메시지 처리 함수
function handlePush(data) {
if (data.type === 'PUSH_NOTIFICATION') {
// 저장소 업데이트: 새 알림을 목록 맨 앞에 추가
notifications.update(items => [{
id: Date.now(),
title: data.title,
body: data.body,
}, ...items]);
}
}
// 컴포넌트 마운트 시 WebBridge 초기화
onMount(() => {
bridge = new WebBridge();
bridge.on('response', handlePush); // 리스너 등록
});
// 컴포넌트 파괴 시 리스너 정리
onDestroy(() => {
if (bridge) bridge.off('response', handlePush);
});
</script>
{#each $notifications as notif (notif.id)}
<div>
<h3>{notif.title}</h3>
<p>{notif.body}</p>
</div>
{/each}
네이티브 앱의 푸시를 WebView에서 쉽게 받으세요