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

npmnpm
Version
1.4.0
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 콜백을 실행합니다.

설치

npm install rnww-plugin-background

빠른 시작

import { registerBackgroundHandlers } from 'rnww-plugin-background';

registerBackgroundHandlers({
  bridge: yourBridgeImplementation,
  platform: { OS: Platform.OS },
});

핵심 개념

이 플러그인은 Headless WebView에 JavaScript 콜백을 주입하여 앱 라이프사이클 이벤트 발생 시 실행합니다.

// 콜백 함수가 WebView에 주입되어 트리거 발생 시 실행됨
bridge.call('registerTask', {
  taskId: 'my-task',
  triggers: ['app_foreground', 'app_background'],
  callback: (trigger, data) => {
    // 이 코드는 Headless WebView 내에서 실행됨
    if (trigger === 'app_background') {
      // 앱이 백그라운드로 갈 때 상태 저장
      saveCurrentState();
    } else if (trigger === 'app_foreground') {
      // 앱이 포그라운드로 올 때 데이터 동기화
      syncData();
    }
  },
  notification: {
    title: '백그라운드 실행 중',
    body: '서비스가 실행 중입니다'
  }
});

Bridge Handlers

registerTask

백그라운드 작업을 등록합니다.

const result = await bridge.call('registerTask', {
  taskId: string;           // 필수: 작업 고유 ID
  triggers?: TriggerType[]; // 트리거 목록 ['app_foreground', 'app_background']
  callback?: BackgroundCallback;  // WebView에서 실행될 콜백
  onEvent?: TaskCallback;   // 네이티브 이벤트 리스너
  onTerminate?: TaskCallback; // 종료 직전 콜백
  callbackId?: string;      // 이벤트 추적용 식별자
  notification?: NotificationConfig; // Android 필수
});

// 결과
// { success: true, taskId: 'my-task' }
// { success: false, error: 'TASK_ALREADY_EXISTS' }

unregisterTask

등록된 작업을 해제합니다.

const result = await bridge.call('unregisterTask', { taskId: 'my-task' });
// { success: true, taskId: 'my-task' }

startTask

작업을 시작합니다. Android에서는 Foreground Service가 시작됩니다.

const result = await bridge.call('startTask', { taskId: 'my-task' });
// { success: true, taskId: 'my-task' }
// { success: false, error: 'NOTIFICATION_PERMISSION_DENIED' }

stopTask

작업을 중지합니다.

const result = await bridge.call('stopTask', { taskId: 'my-task' });
// { success: true, taskId: 'my-task' }

stopAllTasks

모든 작업을 중지합니다.

const result = await bridge.call('stopAllTasks');
// { success: true }

updateNotification

실행 중인 알림 내용을 업데이트합니다. (Android only)

const result = await bridge.call('updateNotification', {
  taskId: 'my-task',
  title: '동기화 중',
  body: '작업 진행 중...',
  silent: true  // 업데이트 시 소리/진동 없음 (기본값: true)
});
// { success: true }

getTaskStatus

특정 작업의 상태를 조회합니다.

const result = await bridge.call('getTaskStatus', { taskId: 'my-task' });
// {
//   success: true,
//   status: {
//     taskId: 'my-task',
//     isRunning: true,
//     mode: 'persistent',
//     startedAt: 1234567890000
//   }
// }

getAllTasksStatus

모든 작업의 상태를 조회합니다.

const result = await bridge.call('getAllTasksStatus');
// {
//   success: true,
//   tasks: [...],
//   isAnyRunning: true
// }

checkBackgroundPermission

백그라운드 권한 상태를 확인합니다.

const result = await bridge.call('checkBackgroundPermission');
// {
//   success: true,
//   canRunBackground: true,
//   batteryOptimizationExempt: true,  // Android
//   notificationPermission: true,     // Android 13+
//   requiredPermissions: [],
//   deniedPermissions: []
// }

requestBackgroundPermission

백그라운드 권한을 요청합니다. (배터리 최적화 예외)

const result = await bridge.call('requestBackgroundPermission');

checkNotificationPermission

알림 권한을 확인합니다. (Android 13+ 필수)

const result = await bridge.call('checkNotificationPermission');
// { success: true, granted: true, canAskAgain: false }

requestNotificationPermission

알림 권한을 요청합니다.

const result = await bridge.call('requestNotificationPermission');
// { success: true, granted: true, canAskAgain: false }

setTaskData

작업별 데이터를 저장합니다. (SharedPreferences/UserDefaults)

const result = await bridge.call('setTaskData', {
  taskId: 'my-task',
  data: { lastSync: Date.now(), count: 5 }
});
// { success: true }

getTaskData

작업별 데이터를 조회합니다.

const result = await bridge.call('getTaskData', { taskId: 'my-task' });
// { success: true, data: { lastSync: 1234567890, count: 5 } }

removeTaskData

작업별 데이터를 삭제합니다.

const result = await bridge.call('removeTaskData', { taskId: 'my-task' });
// { success: true }

disposeBackgroundHandlers

브릿지 핸들러와 리소스를 정리합니다.

await bridge.call('disposeBackgroundHandlers');
// { success: true }

트리거 시스템

지원 트리거

트리거설명
app_foreground앱이 포그라운드로 전환
app_background앱이 백그라운드로 전환

사용 예시

bridge.call('registerTask', {
  taskId: 'lifecycle-task',
  triggers: ['app_foreground', 'app_background'],
  callback: (trigger, data) => {
    console.log(`트리거: ${trigger}, 시간: ${data.timestamp}`);

    if (trigger === 'app_background') {
      // 앱이 백그라운드로 갈 때 상태 저장
      saveCurrentState();
    } else if (trigger === 'app_foreground') {
      // 앱이 포그라운드로 올 때 데이터 동기화
      syncData();
    }
  },
  notification: { title: 'Service', body: 'Running...' }
});

콜백 시스템

callback (WebView 콜백)

callback은 Headless WebView 내에서 실행됩니다.

callback: (trigger: TriggerType, data: TriggerData) => void

중요 제한사항: 클로저 사용 불가

// ❌ 작동 안 함 - 외부 변수 참조
const API_URL = 'https://api.example.com';
callback: (trigger, data) => {
  fetch(API_URL);  // API_URL은 WebView에서 undefined!
}

// ✅ 올바른 방법 - 값을 직접 인라인
callback: (trigger, data) => {
  fetch('https://api.example.com');
}

onEvent (네이티브 이벤트)

onEvent는 브릿지 레벨에서 네이티브 이벤트를 수신합니다.

bridge.call('registerTask', {
  taskId: 'my-task',
  onEvent: (event) => {
    switch (event.type) {
      case 'started':
        console.log('작업 시작됨');
        break;
      case 'stopped':
        console.log('작업 중지됨');
        break;
      case 'trigger':
        console.log(`트리거 발생: ${event.trigger}`);
        break;
      case 'terminated':
        console.log('비정상 종료 감지');
        break;
    }
  },
  // ...
});

onTerminate (종료 콜백)

서비스 종료 직전에 실행됩니다.

bridge.call('registerTask', {
  taskId: 'my-task',
  onTerminate: async (event) => {
    // 종료 직전 상태 저장 (약 5초 제한)
    await bridge.call('setTaskData', {
      taskId: event.taskId,
      data: { terminatedAt: Date.now() }
    });
  },
  // ...
});

주의: 프로세스가 즉시 kill되면 호출되지 않을 수 있습니다. 비정상 종료는 다음 앱 실행 시 terminated 이벤트로 감지됩니다.

callbackId

여러 작업의 이벤트를 구분하기 위한 식별자입니다.

bridge.call('registerTask', {
  taskId: 'task-1',
  callbackId: 'sync-callback',
  // ...
});

// 이벤트에서 callbackId로 구분
bridge.on('onTaskEvent', (event) => {
  if (event.callbackId === 'sync-callback') {
    // task-1의 이벤트
  }
});

알림 설정 (Android)

NotificationConfig

interface NotificationConfig {
  taskId?: string;          // updateNotification 시 대상 지정
  title: string;            // 알림 제목
  body: string;             // 알림 본문
  icon?: string;            // Android drawable 리소스명
  color?: string;           // 아이콘 색상 (hex: '#4CAF50')
  priority?: 'min' | 'low' | 'default' | 'high' | 'max';
  ongoing?: boolean;        // 지속 알림 (스와이프 불가, 기본값: true)
  silent?: boolean;         // 무음 알림 (기본값: false, updateNotification 시 true)
  channelId?: string;       // Android 8.0+
  channelName?: string;
  channelDescription?: string;
}

예시

notification: {
  title: '백그라운드 서비스',
  body: '실행 중입니다',
  icon: 'ic_notification',
  color: '#4CAF50',
  priority: 'low',
  ongoing: true,
  silent: false,
  channelId: 'my_channel',
  channelName: 'Background Service'
}

이벤트

TaskEvent 구조

interface TaskEvent {
  taskId: string;
  type: 'started' | 'stopped' | 'trigger' | 'terminating' | 'terminated';
  trigger?: TriggerType;      // type이 'trigger'일 때
  reason?: 'unexpected';      // type이 'terminated'일 때
  lastStartedAt?: number;     // type이 'terminated'일 때
  timestamp: number;
  callbackId?: string;
}

이벤트 타입

타입설명
started작업 시작됨
stopped작업 중지됨
trigger트리거 발생 (WebView callback 호출됨)
terminating서비스 종료 직전 (best-effort)
terminated비정상 종료 감지 (앱 재시작 시)

에러 타입

type BackgroundError =
  | 'TASK_NOT_FOUND'              // 작업을 찾을 수 없음
  | 'TASK_ALREADY_EXISTS'         // 이미 등록된 작업
  | 'TASK_ALREADY_RUNNING'        // 이미 실행 중
  | 'NOTIFICATION_PERMISSION_DENIED'  // 알림 권한 없음 (Android 13+)
  | 'INVALID_INPUT'               // 잘못된 입력
  | 'UNKNOWN';                    // 알 수 없는 에러

전체 사용 예시

async function setupBackgroundService() {
  // 1. 권한 확인 (Android 13+)
  const notifPerm = await bridge.call('checkNotificationPermission');
  if (!notifPerm.granted) {
    const result = await bridge.call('requestNotificationPermission');
    if (!result.granted) {
      alert('알림 권한이 필요합니다');
      return;
    }
  }

  // 2. 작업 등록
  const registerResult = await bridge.call('registerTask', {
    taskId: 'lifecycle-task',
    triggers: ['app_foreground', 'app_background'],

    // WebView에서 실행될 콜백
    callback: (trigger, data) => {
      console.log('[WebView] Trigger:', trigger);

      if (trigger === 'app_background') {
        // 백그라운드 진입 시 처리
        fetch('https://api.example.com/sync', {
          method: 'POST',
          body: JSON.stringify({ event: 'background', timestamp: data.timestamp })
        });
      }
    },

    // 네이티브 이벤트 리스너
    onEvent: (event) => {
      console.log('[Native] Event:', event.type);
    },

    // 종료 직전 콜백
    onTerminate: (event) => {
      console.log('Task terminating:', event.taskId);
    },

    notification: {
      title: '백그라운드 서비스',
      body: '앱이 백그라운드에서 실행 중입니다',
      icon: 'ic_notification',
      color: '#4CAF50',
      priority: 'low',
      ongoing: true,
      silent: true
    }
  });

  if (!registerResult.success) {
    console.error('등록 실패:', registerResult.error);
    return;
  }

  // 3. 작업 시작
  const startResult = await bridge.call('startTask', { taskId: 'lifecycle-task' });
  if (!startResult.success) {
    console.error('시작 실패:', startResult.error);
  }
}

// 작업 중지
async function stopService() {
  await bridge.call('stopTask', { taskId: 'lifecycle-task' });
}

플랫폼별 설정

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" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Android 13+ (API 33):

  • POST_NOTIFICATIONS 런타임 권한 필수
  • 권한 없이 startTask 호출 시 NOTIFICATION_PERMISSION_DENIED 에러

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>

실행 플로우

Task 시작 플로우

1. registerTask() - 태스크 등록 (TaskManager에 저장)
2. startTask() - 태스크 시작
   - Android: Foreground Service 시작
   - HeadlessWebView 초기화
   - callback 함수 WebView에 주입
3. "started" 이벤트 발생

라이프사이클 트리거 플로우

1. 앱 포그라운드/백그라운드 전환 감지
   - Android: OnActivityEntersForeground/OnActivityEntersBackground
   - iOS: willEnterForegroundNotification/didEnterBackgroundNotification
2. 실행 중인 태스크 중 해당 트리거를 가진 태스크 탐색
3. HeadlessWebView에서 callback 실행
4. "trigger" 이벤트 발생

종료 플로우

1. stopTask() 또는 시스템에 의한 종료
2. "terminating" 이벤트 발생 (best-effort)
3. 리소스 정리
4. "stopped" 이벤트 발생

비정상 종료 감지

1. 앱이 강제 종료됨 (isRunning=true 상태로 저장됨)
2. 앱 재시작 시 TaskManager가 이전 상태 복원
3. isRunning=true인 태스크 감지
4. "terminated" 이벤트 발생 (reason: 'unexpected')

제한사항

1. 클로저 변수 사용 불가

callback 함수는 toString()으로 변환되어 WebView에 주입됩니다. 외부 스코프 변수는 WebView에서 접근할 수 없습니다.

// ❌ 작동 안 함
const config = { url: 'https://api.com' };
callback: (trigger, data) => {
  fetch(config.url);  // config is undefined
}

// ✅ 올바른 방법
callback: (trigger, data) => {
  fetch('https://api.com');
}

2. TypeScript 지원

TypeScript 타입 어노테이션은 컴파일 시 제거되므로 정상 작동합니다.

// ✅ 정상 작동
callback: (trigger: TriggerType, data: TriggerData) => {
  // ...
}

3. 비동기 콜백

async/await 사용 가능합니다.

// ✅ 정상 작동
callback: async (trigger, data) => {
  const response = await fetch('https://api.com');
  // ...
}

4. 네트워크 요청

Headless WebView에서 fetch, XMLHttpRequest, WebSocket 사용 가능합니다.

라이선스

MIT

Keywords

react-native

FAQs

Package last updated on 04 Feb 2026

Related posts