rnww-plugin-wifi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +841 -0
  2. package/lib/bridge/background-bridge.d.ts +22 -0
  3. package/lib/bridge/background-bridge.d.ts.map +1 -0
  4. package/lib/bridge/background-bridge.js +493 -0
  5. package/lib/bridge/index.d.ts +6 -0
  6. package/lib/bridge/index.d.ts.map +1 -0
  7. package/lib/bridge/index.js +23 -0
  8. package/lib/bridge/wifi-bridge.d.ts +16 -0
  9. package/lib/bridge/wifi-bridge.d.ts.map +1 -0
  10. package/lib/bridge/wifi-bridge.js +202 -0
  11. package/lib/modules/index.d.ts +41 -0
  12. package/lib/modules/index.d.ts.map +1 -0
  13. package/lib/modules/index.js +150 -0
  14. package/lib/types/background-module.d.ts +291 -0
  15. package/lib/types/background-module.d.ts.map +1 -0
  16. package/lib/types/background-module.js +5 -0
  17. package/lib/types/bridge.d.ts +22 -0
  18. package/lib/types/bridge.d.ts.map +1 -0
  19. package/lib/types/bridge.js +5 -0
  20. package/lib/types/index.d.ts +7 -0
  21. package/lib/types/index.d.ts.map +1 -0
  22. package/lib/types/index.js +5 -0
  23. package/lib/types/platform.d.ts +10 -0
  24. package/lib/types/platform.d.ts.map +1 -0
  25. package/lib/types/platform.js +2 -0
  26. package/lib/types/wifi-module.d.ts +146 -0
  27. package/lib/types/wifi-module.d.ts.map +1 -0
  28. package/lib/types/wifi-module.js +6 -0
  29. package/package.json +42 -0
  30. package/src/modules/android/build.gradle +64 -0
  31. package/src/modules/android/src/main/AndroidManifest.xml +28 -0
  32. package/src/modules/android/src/main/java/expo/modules/customwifi/WifiModule.kt +517 -0
  33. package/src/modules/android/src/main/java/expo/modules/customwifi/WifiStateReceiver.kt +46 -0
  34. package/src/modules/expo-module.config.json +9 -0
  35. package/src/modules/index.ts +166 -0
  36. package/src/modules/ios/WifiModule.swift +399 -0
package/README.md ADDED
@@ -0,0 +1,841 @@
1
+ # RNWW Plugin Background
2
+
3
+ React Native WebView 백그라운드 실행 제어 플러그인
4
+
5
+ ## 설치
6
+
7
+ ```bash
8
+ npm install rnww-plugin-background
9
+ ```
10
+
11
+ ## 빠른 시작
12
+
13
+ ```typescript
14
+ import { registerBackgroundHandlers } from 'rnww-plugin-background';
15
+
16
+ registerBackgroundHandlers({
17
+ bridge: yourBridgeImplementation,
18
+ platform: { OS: Platform.OS },
19
+ });
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Bridge Handlers
25
+
26
+ ### registerTask
27
+
28
+ 백그라운드 작업을 등록합니다.
29
+
30
+ ```typescript
31
+ bridge.call('registerTask', {
32
+ taskId: 'sync-task',
33
+ mode: 'persistent',
34
+ interval: 60000,
35
+ triggers: ['network_change', 'battery_low'],
36
+ callbackId: 'my-callback',
37
+ callback: (event) => {
38
+ console.log('Task event:', event);
39
+ },
40
+ notification: {
41
+ title: '백그라운드 실행 중',
42
+ body: '동기화 진행 중...',
43
+ color: '#4CAF50',
44
+ priority: 'high',
45
+ ongoing: true,
46
+ progress: { current: 0, max: 100 },
47
+ actions: [
48
+ {
49
+ id: 'pause',
50
+ title: '일시정지',
51
+ onPress: (actionId, taskId) => {
52
+ console.log(`Action ${actionId} clicked for task ${taskId}`);
53
+ }
54
+ }
55
+ ]
56
+ }
57
+ });
58
+ ```
59
+
60
+ ### startTask / stopTask
61
+
62
+ 작업을 시작하거나 중지합니다.
63
+
64
+ ```typescript
65
+ bridge.call('startTask', { taskId: 'sync-task' });
66
+ bridge.call('stopTask', { taskId: 'sync-task' });
67
+ bridge.call('stopAllTasks');
68
+ ```
69
+
70
+ ### unregisterTask
71
+
72
+ 등록된 작업을 해제합니다.
73
+
74
+ ```typescript
75
+ bridge.call('unregisterTask', { taskId: 'sync-task' });
76
+ ```
77
+
78
+ ### updateNotification
79
+
80
+ 알림 내용을 동적으로 업데이트합니다.
81
+
82
+ ```typescript
83
+ bridge.call('updateNotification', {
84
+ taskId: 'sync-task',
85
+ title: '동기화 중',
86
+ body: '50% 완료',
87
+ progress: { current: 50, max: 100 },
88
+ actions: [
89
+ {
90
+ id: 'cancel',
91
+ title: '취소',
92
+ onPress: (actionId, taskId) => {
93
+ bridge.call('stopTask', { taskId });
94
+ }
95
+ }
96
+ ]
97
+ });
98
+ ```
99
+
100
+ ### getTaskStatus / getAllTasksStatus
101
+
102
+ 작업 상태를 조회합니다.
103
+
104
+ ```typescript
105
+ const status = await bridge.call('getTaskStatus', { taskId: 'sync-task' });
106
+ const allStatus = await bridge.call('getAllTasksStatus');
107
+ ```
108
+
109
+ ### checkBackgroundPermission / requestBackgroundPermission
110
+
111
+ 백그라운드 권한을 확인하고 요청합니다.
112
+
113
+ ```typescript
114
+ const permission = await bridge.call('checkBackgroundPermission');
115
+ if (!permission.canRunBackground) {
116
+ await bridge.call('requestBackgroundPermission');
117
+ }
118
+ ```
119
+
120
+ ### disposeBackgroundHandlers
121
+
122
+ 브릿지 핸들러와 리소스를 정리합니다.
123
+
124
+ ```typescript
125
+ bridge.call('disposeBackgroundHandlers');
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 콜백 시스템
131
+
132
+ ### Task Callback
133
+
134
+ 작업 등록 시 `callback` 함수를 지정하면 해당 작업의 모든 이벤트를 수신합니다.
135
+
136
+ ```typescript
137
+ bridge.call('registerTask', {
138
+ taskId: 'my-task',
139
+ mode: 'persistent',
140
+ callback: (event) => {
141
+ switch (event.type) {
142
+ case 'started':
143
+ console.log('작업 시작됨');
144
+ break;
145
+ case 'stopped':
146
+ console.log('작업 중지됨');
147
+ break;
148
+ case 'trigger':
149
+ console.log(`트리거 발생: ${event.trigger}`, event.data);
150
+ break;
151
+ case 'action':
152
+ console.log(`액션 클릭: ${event.actionId}`);
153
+ break;
154
+ case 'error':
155
+ console.error('에러 발생:', event.error);
156
+ break;
157
+ }
158
+ },
159
+ notification: { title: 'Task', body: 'Running...' }
160
+ });
161
+ ```
162
+
163
+ ### Action Callback
164
+
165
+ 각 알림 액션 버튼에 `onPress` 콜백을 개별 지정할 수 있습니다.
166
+
167
+ ```typescript
168
+ bridge.call('registerTask', {
169
+ taskId: 'download-task',
170
+ mode: 'persistent',
171
+ notification: {
172
+ title: '다운로드 중',
173
+ body: '파일 다운로드...',
174
+ actions: [
175
+ {
176
+ id: 'pause',
177
+ title: '일시정지',
178
+ icon: 'ic_pause',
179
+ dismissOnPress: false,
180
+ onPress: async (actionId, taskId) => {
181
+ await pauseDownload();
182
+ bridge.call('updateNotification', {
183
+ taskId,
184
+ title: '일시정지됨',
185
+ body: '다운로드가 일시정지되었습니다'
186
+ });
187
+ }
188
+ },
189
+ {
190
+ id: 'cancel',
191
+ title: '취소',
192
+ dismissOnPress: true,
193
+ bringToForeground: false,
194
+ onPress: async (actionId, taskId) => {
195
+ await cancelDownload();
196
+ bridge.call('stopTask', { taskId });
197
+ }
198
+ }
199
+ ]
200
+ }
201
+ });
202
+ ```
203
+
204
+ ### Callback ID
205
+
206
+ `callbackId`를 지정하면 이벤트 수신 시 해당 ID가 함께 전달되어 여러 작업의 이벤트를 구분할 수 있습니다.
207
+
208
+ ```typescript
209
+ // 작업 등록
210
+ bridge.call('registerTask', {
211
+ taskId: 'task-1',
212
+ callbackId: 'sync-callback',
213
+ // ...
214
+ });
215
+
216
+ bridge.call('registerTask', {
217
+ taskId: 'task-2',
218
+ callbackId: 'upload-callback',
219
+ // ...
220
+ });
221
+
222
+ // 이벤트 수신
223
+ bridge.on('onTaskEvent', (event) => {
224
+ if (event.callbackId === 'sync-callback') {
225
+ // task-1의 이벤트 처리
226
+ } else if (event.callbackId === 'upload-callback') {
227
+ // task-2의 이벤트 처리
228
+ }
229
+ });
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 트리거 시스템
235
+
236
+ ### 트리거 종류
237
+
238
+ | 트리거 | 설명 |
239
+ |--------|------|
240
+ | `interval` | 주기적 실행 (interval 옵션 사용) |
241
+ | `network_change` | 네트워크 상태 변경 (연결/해제) |
242
+ | `location_change` | 위치 변경 (significant location change) |
243
+ | `time_trigger` | 예약된 시간에 실행 |
244
+ | `battery_low` | 배터리 부족 (기본 15% 이하) |
245
+ | `battery_okay` | 배터리 정상 복귀 |
246
+ | `battery_charging` | 충전 시작 |
247
+ | `battery_discharging` | 충전 해제 |
248
+ | `app_foreground` | 앱이 포그라운드로 전환 |
249
+ | `app_background` | 앱이 백그라운드로 전환 |
250
+ | `app_terminate` | 앱 종료 시 |
251
+ | `custom` | 사용자 정의 트리거 |
252
+
253
+ ### 간단한 사용법
254
+
255
+ 문자열로 트리거 타입만 지정:
256
+
257
+ ```typescript
258
+ bridge.call('registerTask', {
259
+ taskId: 'my-task',
260
+ mode: 'persistent',
261
+ triggers: ['network_change', 'battery_low', 'app_background'],
262
+ // ...
263
+ });
264
+ ```
265
+
266
+ ### 상세 설정
267
+
268
+ 객체로 트리거별 옵션 지정:
269
+
270
+ ```typescript
271
+ bridge.call('registerTask', {
272
+ taskId: 'my-task',
273
+ mode: 'persistent',
274
+ triggers: [
275
+ // 배터리 30% 이하일 때 트리거
276
+ {
277
+ type: 'battery_low',
278
+ options: { threshold: 30 }
279
+ },
280
+ // WiFi 연결 변경만 감지
281
+ {
282
+ type: 'network_change',
283
+ options: { networkTypes: ['wifi'] }
284
+ },
285
+ // 100m 이상 이동 시 트리거
286
+ {
287
+ type: 'location_change',
288
+ options: { minDistance: 100 }
289
+ },
290
+ // 사용자 정의 트리거
291
+ {
292
+ type: 'custom',
293
+ customId: 'my-custom-trigger'
294
+ }
295
+ ],
296
+ callback: (event) => {
297
+ if (event.type === 'trigger') {
298
+ switch (event.trigger) {
299
+ case 'battery_low':
300
+ console.log('배터리 레벨:', event.data?.batteryLevel);
301
+ break;
302
+ case 'network_change':
303
+ console.log('네트워크:', event.data?.networkType, event.data?.isConnected);
304
+ break;
305
+ case 'location_change':
306
+ console.log('위치:', event.data?.location);
307
+ break;
308
+ case 'custom':
309
+ console.log('커스텀 트리거:', event.customTriggerId);
310
+ break;
311
+ }
312
+ }
313
+ },
314
+ // ...
315
+ });
316
+ ```
317
+
318
+ ### 트리거 옵션 상세
319
+
320
+ #### battery_low / battery_okay
321
+
322
+ ```typescript
323
+ {
324
+ type: 'battery_low',
325
+ options: {
326
+ threshold: 20 // 배터리 임계값 % (기본: 15)
327
+ }
328
+ }
329
+ ```
330
+
331
+ 이벤트 데이터:
332
+ ```typescript
333
+ event.data?.batteryLevel // 현재 배터리 레벨 (%)
334
+ ```
335
+
336
+ #### network_change
337
+
338
+ ```typescript
339
+ {
340
+ type: 'network_change',
341
+ options: {
342
+ networkTypes: ['wifi', 'cellular'] // 감지할 네트워크 타입
343
+ }
344
+ }
345
+ ```
346
+
347
+ 이벤트 데이터:
348
+ ```typescript
349
+ event.data?.networkType // 'wifi' | 'cellular' | 'ethernet' | 'none'
350
+ event.data?.isConnected // 연결 상태 (boolean)
351
+ ```
352
+
353
+ #### location_change
354
+
355
+ ```typescript
356
+ {
357
+ type: 'location_change',
358
+ options: {
359
+ minDistance: 50 // 최소 이동 거리 (미터)
360
+ }
361
+ }
362
+ ```
363
+
364
+ 이벤트 데이터:
365
+ ```typescript
366
+ event.data?.location // { latitude: number, longitude: number }
367
+ ```
368
+
369
+ #### time_trigger
370
+
371
+ 예약 시간에 트리거. `scheduledTime` 필드와 함께 사용:
372
+
373
+ ```typescript
374
+ bridge.call('registerTask', {
375
+ taskId: 'scheduled-task',
376
+ mode: 'efficient',
377
+ triggers: ['time_trigger'],
378
+ scheduledTime: Date.now() + 3600000, // 1시간 후
379
+ // ...
380
+ });
381
+ ```
382
+
383
+ #### custom
384
+
385
+ 사용자 정의 트리거. 네이티브 측에서 직접 발생시킬 수 있음:
386
+
387
+ ```typescript
388
+ {
389
+ type: 'custom',
390
+ customId: 'my-sync-trigger' // 고유 식별자
391
+ }
392
+ ```
393
+
394
+ 이벤트에서 `event.customTriggerId`로 식별 가능.
395
+
396
+ ---
397
+
398
+ ## 알림 설정
399
+
400
+ ### 기본 알림
401
+
402
+ ```typescript
403
+ notification: {
404
+ title: '백그라운드 작업',
405
+ body: '작업이 실행 중입니다',
406
+ icon: 'ic_notification', // Android drawable 리소스명
407
+ }
408
+ ```
409
+
410
+ ### 스타일 옵션
411
+
412
+ ```typescript
413
+ notification: {
414
+ title: '동기화',
415
+ body: '데이터 동기화 중...',
416
+
417
+ // 아이콘/강조 색상 (hex)
418
+ color: '#4CAF50',
419
+
420
+ // 우선순위
421
+ // 'min': 최소 (무음, 상태바만)
422
+ // 'low': 낮음 (무음)
423
+ // 'default': 기본
424
+ // 'high': 높음 (헤드업 알림)
425
+ // 'max': 최대 (긴급)
426
+ priority: 'high',
427
+
428
+ // 지속 알림 (사용자가 스와이프로 닫을 수 없음)
429
+ // persistent 모드에서는 항상 true
430
+ ongoing: true,
431
+ }
432
+ ```
433
+
434
+ ### 진행 상태바
435
+
436
+ ```typescript
437
+ notification: {
438
+ title: '다운로드 중',
439
+ body: '50% 완료',
440
+ progress: {
441
+ current: 50,
442
+ max: 100,
443
+ indeterminate: false // true면 무한 진행 표시
444
+ }
445
+ }
446
+ ```
447
+
448
+ 무한 진행 표시 (로딩):
449
+
450
+ ```typescript
451
+ progress: {
452
+ current: 0,
453
+ max: 100,
454
+ indeterminate: true
455
+ }
456
+ ```
457
+
458
+ ### 액션 버튼
459
+
460
+ 최대 3개까지 지원:
461
+
462
+ ```typescript
463
+ notification: {
464
+ title: '음악 재생 중',
465
+ body: 'Now Playing...',
466
+ actions: [
467
+ {
468
+ id: 'prev',
469
+ title: '이전',
470
+ icon: 'ic_prev', // Android drawable (선택)
471
+ dismissOnPress: false, // 클릭 시 알림 유지
472
+ bringToForeground: false, // 앱을 포그라운드로 가져오지 않음
473
+ onPress: (actionId, taskId) => {
474
+ playPrevious();
475
+ }
476
+ },
477
+ {
478
+ id: 'pause',
479
+ title: '일시정지',
480
+ icon: 'ic_pause',
481
+ onPress: (actionId, taskId) => {
482
+ togglePlayPause();
483
+ }
484
+ },
485
+ {
486
+ id: 'next',
487
+ title: '다음',
488
+ icon: 'ic_next',
489
+ onPress: (actionId, taskId) => {
490
+ playNext();
491
+ }
492
+ }
493
+ ]
494
+ }
495
+ ```
496
+
497
+ ### 채널 설정 (Android 8.0+)
498
+
499
+ ```typescript
500
+ notification: {
501
+ title: '알림',
502
+ body: '내용',
503
+ channelId: 'sync_channel',
504
+ channelName: '동기화 알림',
505
+ channelDescription: '백그라운드 동기화 알림을 표시합니다'
506
+ }
507
+ ```
508
+
509
+ ---
510
+
511
+ ## 실행 모드
512
+
513
+ ### persistent 모드
514
+
515
+ 포그라운드 서비스로 항상 실행됩니다. 알림이 필수입니다.
516
+
517
+ ```typescript
518
+ bridge.call('registerTask', {
519
+ taskId: 'always-on',
520
+ mode: 'persistent',
521
+ interval: 10000, // 최소 1초 (1000ms)
522
+ notification: { // 필수
523
+ title: '실행 중',
524
+ body: '백그라운드 서비스 동작 중'
525
+ }
526
+ });
527
+ ```
528
+
529
+ **특징:**
530
+ - 시스템에 의해 종료되지 않음 (높은 우선순위)
531
+ - 항상 알림 표시 (ongoing: true 강제)
532
+ - interval 최소값: 1000ms (1초)
533
+ - 배터리 사용량 높음
534
+
535
+ ### efficient 모드
536
+
537
+ 시스템이 관리하는 효율적 실행입니다. (WorkManager/BGTaskScheduler)
538
+
539
+ ```typescript
540
+ bridge.call('registerTask', {
541
+ taskId: 'periodic-sync',
542
+ mode: 'efficient',
543
+ interval: 900000, // 최소 15분 (900000ms)
544
+ triggers: ['network_change'],
545
+ notification: { // 선택
546
+ title: '동기화',
547
+ body: '대기 중...'
548
+ }
549
+ });
550
+ ```
551
+
552
+ **특징:**
553
+ - 시스템이 배터리/리소스에 따라 실행 시점 조정
554
+ - interval 최소값: 900000ms (15분, 시스템 제한)
555
+ - 정확한 타이밍 보장 안됨
556
+ - 배터리 효율적
557
+
558
+ ---
559
+
560
+ ## 이벤트
561
+
562
+ ### onTaskEvent
563
+
564
+ 모든 작업 이벤트를 수신합니다.
565
+
566
+ ```typescript
567
+ bridge.on('onTaskEvent', (event) => {
568
+ console.log('Event:', event);
569
+ });
570
+ ```
571
+
572
+ ### TaskEvent 구조
573
+
574
+ ```typescript
575
+ interface TaskEvent {
576
+ taskId: string; // 작업 ID
577
+ callbackId?: string; // 등록 시 지정한 콜백 ID
578
+ type: 'started' | 'stopped' | 'restart' | 'error' | 'trigger' | 'action';
579
+ trigger?: TriggerType; // 트리거 종류 (type이 'trigger'일 때)
580
+ customTriggerId?: string; // custom 트리거 식별자
581
+ actionId?: string; // 액션 버튼 ID (type이 'action'일 때)
582
+ error?: string; // 에러 메시지 (type이 'error'일 때)
583
+ data?: { // 트리거 관련 데이터
584
+ batteryLevel?: number;
585
+ networkType?: 'wifi' | 'cellular' | 'ethernet' | 'none';
586
+ isConnected?: boolean;
587
+ location?: { latitude: number; longitude: number };
588
+ [key: string]: unknown;
589
+ };
590
+ timestamp: number; // 타임스탬프
591
+ }
592
+ ```
593
+
594
+ ---
595
+
596
+ ## 타입 정의
597
+
598
+ ### BackgroundTask
599
+
600
+ ```typescript
601
+ interface BackgroundTask {
602
+ taskId: string;
603
+ mode: 'persistent' | 'efficient';
604
+ interval?: number;
605
+ triggers?: TriggerConfig[];
606
+ scheduledTime?: number;
607
+ callbackId?: string;
608
+ callback?: (event: TaskEvent) => void | Promise<void>;
609
+ notification?: NotificationConfig;
610
+ }
611
+ ```
612
+
613
+ ### TriggerConfig
614
+
615
+ ```typescript
616
+ type TriggerType =
617
+ | 'interval'
618
+ | 'network_change'
619
+ | 'location_change'
620
+ | 'time_trigger'
621
+ | 'battery_low'
622
+ | 'battery_okay'
623
+ | 'battery_charging'
624
+ | 'battery_discharging'
625
+ | 'app_foreground'
626
+ | 'app_background'
627
+ | 'app_terminate'
628
+ | 'custom';
629
+
630
+ type TriggerConfig = TriggerType | {
631
+ type: TriggerType;
632
+ customId?: string;
633
+ options?: {
634
+ threshold?: number;
635
+ minDistance?: number;
636
+ networkTypes?: Array<'wifi' | 'cellular' | 'ethernet'>;
637
+ };
638
+ };
639
+ ```
640
+
641
+ ### NotificationConfig
642
+
643
+ ```typescript
644
+ interface NotificationConfig {
645
+ taskId?: string;
646
+ title: string;
647
+ body: string;
648
+ icon?: string;
649
+ color?: string;
650
+ priority?: 'min' | 'low' | 'default' | 'high' | 'max';
651
+ ongoing?: boolean;
652
+ progress?: {
653
+ current: number;
654
+ max: number;
655
+ indeterminate?: boolean;
656
+ };
657
+ actions?: NotificationAction[];
658
+ channelId?: string;
659
+ channelName?: string;
660
+ channelDescription?: string;
661
+ }
662
+ ```
663
+
664
+ ### NotificationAction
665
+
666
+ ```typescript
667
+ interface NotificationAction {
668
+ id: string;
669
+ title: string;
670
+ icon?: string;
671
+ onPress?: (actionId: string, taskId: string) => void | Promise<void>;
672
+ dismissOnPress?: boolean; // 기본: true
673
+ bringToForeground?: boolean; // 기본: false
674
+ }
675
+ ```
676
+
677
+ ### BackgroundError
678
+
679
+ ```typescript
680
+ type BackgroundError =
681
+ | 'TASK_NOT_FOUND'
682
+ | 'TASK_ALREADY_EXISTS'
683
+ | 'TASK_ALREADY_RUNNING'
684
+ | 'TASK_NOT_RUNNING'
685
+ | 'PERMISSION_DENIED'
686
+ | 'SYSTEM_RESTRICTED'
687
+ | 'WEBVIEW_INIT_FAILED'
688
+ | 'INVALID_INPUT'
689
+ | 'INVALID_INTERVAL'
690
+ | 'INVALID_TRIGGER'
691
+ | 'NOTIFICATION_REQUIRED'
692
+ | 'UNKNOWN';
693
+ ```
694
+
695
+ ---
696
+
697
+ ## 권한 설정
698
+
699
+ ### Android
700
+
701
+ `AndroidManifest.xml`에 자동 추가:
702
+
703
+ ```xml
704
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
705
+ <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
706
+ ```
707
+
708
+ ### iOS
709
+
710
+ `Info.plist`에 추가:
711
+
712
+ ```xml
713
+ <key>UIBackgroundModes</key>
714
+ <array>
715
+ <string>fetch</string>
716
+ <string>processing</string>
717
+ </array>
718
+
719
+ <key>BGTaskSchedulerPermittedIdentifiers</key>
720
+ <array>
721
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER).background</string>
722
+ </array>
723
+ ```
724
+
725
+ ---
726
+
727
+ ## 예제: 음악 플레이어
728
+
729
+ ```typescript
730
+ // 음악 재생 백그라운드 서비스
731
+ bridge.call('registerTask', {
732
+ taskId: 'music-player',
733
+ mode: 'persistent',
734
+ triggers: [
735
+ 'app_background',
736
+ { type: 'battery_low', options: { threshold: 10 } }
737
+ ],
738
+ callback: (event) => {
739
+ if (event.type === 'trigger' && event.trigger === 'battery_low') {
740
+ // 배터리 부족 시 품질 낮춤
741
+ setStreamQuality('low');
742
+ }
743
+ },
744
+ notification: {
745
+ title: 'Now Playing',
746
+ body: 'Artist - Song Title',
747
+ color: '#1DB954',
748
+ ongoing: true,
749
+ actions: [
750
+ {
751
+ id: 'prev',
752
+ title: '이전',
753
+ icon: 'ic_skip_previous',
754
+ dismissOnPress: false,
755
+ onPress: () => skipToPrevious()
756
+ },
757
+ {
758
+ id: 'play_pause',
759
+ title: '재생/일시정지',
760
+ icon: 'ic_play_pause',
761
+ dismissOnPress: false,
762
+ onPress: () => togglePlayPause()
763
+ },
764
+ {
765
+ id: 'next',
766
+ title: '다음',
767
+ icon: 'ic_skip_next',
768
+ dismissOnPress: false,
769
+ onPress: () => skipToNext()
770
+ }
771
+ ]
772
+ }
773
+ });
774
+
775
+ // 트랙 변경 시 알림 업데이트
776
+ function onTrackChange(track) {
777
+ bridge.call('updateNotification', {
778
+ taskId: 'music-player',
779
+ title: track.title,
780
+ body: `${track.artist} - ${track.album}`,
781
+ });
782
+ }
783
+ ```
784
+
785
+ ## 예제: 파일 동기화
786
+
787
+ ```typescript
788
+ // 효율적 주기 동기화
789
+ bridge.call('registerTask', {
790
+ taskId: 'file-sync',
791
+ mode: 'efficient',
792
+ interval: 1800000, // 30분
793
+ triggers: [
794
+ { type: 'network_change', options: { networkTypes: ['wifi'] } },
795
+ 'battery_charging'
796
+ ],
797
+ callback: async (event) => {
798
+ if (event.type === 'trigger') {
799
+ // WiFi 연결되거나 충전 시작 시 동기화
800
+ await syncFiles();
801
+ }
802
+ },
803
+ notification: {
804
+ title: '동기화 대기 중',
805
+ body: '다음 동기화 예약됨',
806
+ priority: 'low'
807
+ }
808
+ });
809
+
810
+ // 동기화 진행 시 알림 업데이트
811
+ async function syncFiles() {
812
+ const files = await getFilesToSync();
813
+
814
+ for (let i = 0; i < files.length; i++) {
815
+ bridge.call('updateNotification', {
816
+ taskId: 'file-sync',
817
+ title: '동기화 중',
818
+ body: `${files[i].name}`,
819
+ progress: {
820
+ current: i + 1,
821
+ max: files.length
822
+ }
823
+ });
824
+
825
+ await uploadFile(files[i]);
826
+ }
827
+
828
+ bridge.call('updateNotification', {
829
+ taskId: 'file-sync',
830
+ title: '동기화 완료',
831
+ body: `${files.length}개 파일 동기화됨`,
832
+ progress: undefined
833
+ });
834
+ }
835
+ ```
836
+
837
+ ---
838
+
839
+ ## 라이선스
840
+
841
+ MIT