New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

rnww-plugin-background

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rnww-plugin-background

React Native WebView Background Execution Plugin with Expo support

latest
npmnpm
Version
1.7.3
Version published
Weekly downloads
1
-98.18%
Maintainers
1
Weekly downloads
 
Created
Source

RNWW Plugin Background

React Native WebView 백그라운드 실행 플러그인 (Expo Native Module)

Headless WebView를 사용하여 앱 종료 후에도 JavaScript를 독립적으로 실행합니다.

⚠️ 필수 설정

본 프로젝트(RN-Expo-WebApp-Wrapper-Template)에서 npm run config를 실행하여 플러그인 설정에서 **"Enable Headless Bridge"**를 체크해야 정상적으로 동작합니다.

이 옵션을 활성화하면:

  • Headless WebView에 Bridge 스크립트가 자동으로 주입됩니다
  • Headless WebView에서 AppBridge API를 사용할 수 있습니다
  • 기존 Bridge 핸들러와 연동됩니다

설치

npm install rnww-plugin-background

빠른 시작

// 태스크 등록 - callback은 .toString()으로 전달해야 함!
await AppBridge.call('backgr:registerTask', {
  taskId: 'my-task',
  triggers: ['app_foreground', 'app_background'],
  callback: function(trigger, data) {
    // ⚠️ Headless WebView에서 실행됨 (Foreground WebView와 별개 context)
    console.log('Trigger:', trigger);
    
    if (trigger === 'init') {
      // 초기화 - TaskData에서 이전 상태 복원
      AppBridge.call('backgr:getTaskData', { taskId: data.taskId })
        .then(function(result) {
          window.__counter = result?.data?.counter || 0;
        });
    }
  }.toString(),  // ⚠️ 반드시 .toString() 사용!
  notification: {
    title: '백그라운드 실행 중',
    body: '서비스가 실행 중입니다'
  }
});

// 태스크 시작
await AppBridge.call('backgr:startTask', { taskId: 'my-task' });

핵심 개념

두 개의 분리된 WebView Context

이 플러그인의 핵심은 Foreground WebViewHeadless WebView완전히 별개의 context라는 점입니다.

┌─────────────────────────────────────────────────────────────────┐
│                    Native (React Native)                        │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                   Bridge Handlers                          │  │
│  │   backgr:setTaskData, backgr:getTaskData, api:*, ...      │  │
│  └───────────────────────────────────────────────────────────┘  │
│                    ▲                ▲                           │
│                    │                │                           │
│         postMessage│      postMessage│                          │
│                    │                │                           │
│  ┌─────────────────┴──┐  ┌─────────┴────────────────────────┐  │
│  │ Foreground WebView │  │      Headless WebView            │  │
│  │                    │  │                                  │  │
│  │ • 실제 웹 페이지    │  │ • 빈 페이지 (스크립트만 실행)    │  │
│  │ • 사용자 인터랙션   │  │ • 백그라운드 콜백 실행           │  │
│  │ • window.AppBridge │  │ • window.AppBridge               │  │
│  │   (인스턴스 A)      │  │   (인스턴스 B)                   │  │
│  │                    │  │                                  │  │
│  │ 동일한 코드 주입    │  │ 동일한 코드 주입                 │  │
│  └────────────────────┘  └──────────────────────────────────┘  │
│       앱과 함께 종료           Foreground Service로 유지        │
└─────────────────────────────────────────────────────────────────┘

Context 특징 비교

항목Foreground WebViewHeadless WebView
용도실제 웹페이지 렌더링백그라운드 스크립트 실행
DOM전체 HTML/CSS 렌더링빈 페이지
window 객체독립 인스턴스독립 인스턴스
AppBridge동일한 API (인스턴스 A)동일한 API (인스턴스 B)
변수 공유❌ 불가❌ 불가
localStorage앱 종료 시 유지서비스 종료 시 초기화
생명주기앱과 함께서비스와 함께 (앱 종료 후에도 유지)

AppBridge - 같은 코드, 다른 인스턴스

두 WebView에 동일한 Bridge 클라이언트 코드가 주입되므로:

  • 같은 API 사용 가능: AppBridge.send(), AppBridge.call(), AppBridge.on()
  • 같은 Native 핸들러 호출 가능: backgr:setTaskData, api:*
  • 하지만 변수, 리스너, 상태는 공유되지 않음

TaskData를 통한 Context 동기화

두 WebView 간 데이터 공유는 TaskData API를 통해서만 가능합니다:

Foreground WebView  ←→  Native TaskData Storage  ←→  Headless WebView
     setTaskData() →      [JSON 데이터 저장]       ← setTaskData()
     getTaskData() ←                              → getTaskData()
// Headless에서 데이터 저장
AppBridge.call('backgr:setTaskData', {
  taskId: 'my-task',
  data: { counter: 10, lastUpdate: Date.now() }
});

// Foreground에서 데이터 읽기 (앱 재시작 시)
const result = await AppBridge.call('backgr:getTaskData', { taskId: 'my-task' });
// result.data = { counter: 10, lastUpdate: ... }

트리거 시스템

트리거 종류

트리거호출 시점자동 호출설명
init태스크 시작 직후✅ 항상초기화 로직 실행
terminate태스크 종료 직전✅ 항상상태 저장, 리소스 정리
app_foreground앱 포그라운드 전환❌ triggers 배열 필요데이터 동기화, UI 갱신
app_background앱 백그라운드 전환❌ triggers 배열 필요상태 저장, 네트워크 요청

callback 함수 작성 주의사항

⚠️ callback은 반드시 .toString()으로 전달해야 합니다!

// ✅ 올바른 방법
const myCallback = function(trigger, data) {
  console.log(trigger);
};

await AppBridge.call('backgr:registerTask', {
  taskId: 'my-task',
  callback: myCallback.toString(),  // ✅ .toString() 필수!
  notification: { title: 'Service', body: 'Running' }
});

// ❌ 잘못된 방법 - JSON 직렬화 불가
await AppBridge.call('backgr:registerTask', {
  taskId: 'my-task',
  callback: myCallback,  // ❌ 함수는 JSON으로 전달 불가!
  notification: { title: 'Service', body: 'Running' }
});

완전한 callback 예시

const BACKGROUND_CALLBACK = function(trigger, data) {
  // ⚠️ 이 코드는 Headless WebView에서 실행됨!
  // window, AppBridge, fetch 등 Web API 사용 가능

  if (trigger === 'init') {
    // TaskData에서 이전 상태 복원
    AppBridge.call('backgr:getTaskData', { taskId: data.taskId })
      .then(function(result) {
        if (result && result.data && typeof result.data.counter === 'number') {
          window.__counter = result.data.counter;
        } else {
          window.__counter = 0;
        }
        
        // 주기적 작업 시작
        window.__interval = setInterval(function() {
          window.__counter++;
          
          // TaskData에 상태 저장
          AppBridge.call('backgr:setTaskData', {
            taskId: data.taskId,
            data: { counter: window.__counter, updatedAt: Date.now() }
          });
          
          // 알림 업데이트
          AppBridge.call('backgr:updateNotification', {
            taskId: data.taskId,
            title: 'Counter: ' + window.__counter,
            body: 'Running...'
          });
        }, 1000);
      });
  }

  if (trigger === 'terminate') {
    // 종료 전 정리
    if (window.__interval) {
      clearInterval(window.__interval);
      window.__interval = null;
    }
  }
};

await AppBridge.call('backgr:registerTask', {
  taskId: 'counter-task',
  triggers: ['app_foreground', 'app_background'],
  callback: BACKGROUND_CALLBACK.toString(),
  notification: { title: 'Counter', body: 'Starting...' }
});

Bridge Handlers

모든 핸들러는 backgr: 네임스페이스를 사용합니다.

태스크 생명주기

핸들러설명
backgr:registerTask태스크 등록
backgr:unregisterTask태스크 해제
backgr:startTask태스크 시작
backgr:stopTask태스크 중지
backgr:stopAllTasks모든 태스크 중지

상태 조회

핸들러설명
backgr:getTaskStatus특정 태스크 상태 조회
backgr:getAllTasksStatus전체 태스크 상태 조회

알림 (Android)

핸들러설명
backgr:updateNotification알림 내용 업데이트

권한

핸들러설명
backgr:checkBackgroundPermission백그라운드 권한 확인
backgr:requestBackgroundPermission배터리 최적화 예외 요청
backgr:checkNotificationPermission알림 권한 확인 (Android 13+)
backgr:requestNotificationPermission알림 권한 요청 (Android 13+)

TaskData (Context 동기화)

핸들러설명앱 종료 후
backgr:setTaskData데이터 저장✅ 동작
backgr:getTaskData데이터 조회✅ 동작
backgr:removeTaskData데이터 삭제✅ 동작

WebView 제어 (레거시)

핸들러설명
backgr:attachWebViewWebView 수동 연결
backgr:detachWebViewWebView 연결 해제
backgr:evaluateInMainWebViewMain WebView에서 JS 실행

앱 종료 후 동작

앱이 완전히 종료되어도 Foreground Service 내의 Headless WebView는 계속 동작합니다.

앱 종료 후에도 동작하는 것

  • ✅ callback 함수 (Headless WebView에서 실행)
  • backgr:* 핸들러 (Native에서 직접 처리)
  • ✅ Web API (fetch, setTimeout, setInterval 등)
  • ✅ TaskData 저장/조회

앱 종료 후 동작하지 않는 것

  • ❌ React Native 핸들러 (api:*, auth:* 등)
  • ❌ Foreground WebView와의 직접 통신

Foreground에서 백그라운드 상태 동기화

앱을 다시 열었을 때 백그라운드 상태와 동기화하는 패턴:

// Foreground WebView에서 (앱 재시작 시)
async function syncWithBackground() {
  const TASK_ID = 'my-task';
  
  // 1. 실행 중인 태스크 확인
  const statusResult = await AppBridge.call('backgr:getTaskStatus', { taskId: TASK_ID });
  
  if (statusResult?.status?.isRunning) {
    console.log('🔄 실행 중인 백그라운드 태스크 발견!');
    
    // 2. TaskData에서 현재 상태 읽기
    const dataResult = await AppBridge.call('backgr:getTaskData', { taskId: TASK_ID });
    
    if (dataResult?.data) {
      // 3. UI 업데이트
      setCounter(dataResult.data.counter);
      setLastUpdate(dataResult.data.updatedAt);
      console.log('UI 복원 완료:', dataResult.data);
    }
  }
}

// 앱 로드 시 호출
useEffect(() => {
  syncWithBackground();
}, []);

알림 설정 (Android)

notification: {
  title: string;              // 필수
  body: string;               // 필수
  icon?: string;              // drawable 리소스명
  color?: string;             // '#4CAF50'
  priority?: 'min' | 'low' | 'default' | 'high' | 'max';
  ongoing?: boolean;          // 기본값: true (스와이프 불가)
  silent?: boolean;           // 기본값: false
  channelId?: string;         // Android 8.0+
  channelName?: string;
  channelDescription?: string;
}

에러 타입

에러설명해결 방법
TASK_NOT_FOUND미등록 태스크registerTask 먼저 호출
TASK_ALREADY_EXISTS중복 등록unregisterTask 후 재등록
TASK_ALREADY_RUNNING이미 실행 중getTaskStatus로 확인
NOTIFICATION_PERMISSION_DENIED알림 권한 없음requestNotificationPermission 호출
WEBVIEW_INIT_TIMEOUTHeadless 초기화 실패앱 재시작 또는 스크립트 확인
INVALID_INPUT잘못된 입력유효한 taskId 확인

플랫폼별 설정

Android

AndroidManifest.xml에 자동 추가:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

Android 13+ (API 33): POST_NOTIFICATIONS 런타임 권한 필수

iOS

Info.plist에 추가:

<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>processing</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>expo.modules.custombackground.refresh</string>
</array>

라이선스

MIT

Keywords

react-native

FAQs

Package last updated on 07 Feb 2026

Related posts