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.
- package/README.md +841 -0
- package/lib/bridge/background-bridge.d.ts +22 -0
- package/lib/bridge/background-bridge.d.ts.map +1 -0
- package/lib/bridge/background-bridge.js +493 -0
- package/lib/bridge/index.d.ts +6 -0
- package/lib/bridge/index.d.ts.map +1 -0
- package/lib/bridge/index.js +23 -0
- package/lib/bridge/wifi-bridge.d.ts +16 -0
- package/lib/bridge/wifi-bridge.d.ts.map +1 -0
- package/lib/bridge/wifi-bridge.js +202 -0
- package/lib/modules/index.d.ts +41 -0
- package/lib/modules/index.d.ts.map +1 -0
- package/lib/modules/index.js +150 -0
- package/lib/types/background-module.d.ts +291 -0
- package/lib/types/background-module.d.ts.map +1 -0
- package/lib/types/background-module.js +5 -0
- package/lib/types/bridge.d.ts +22 -0
- package/lib/types/bridge.d.ts.map +1 -0
- package/lib/types/bridge.js +5 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +5 -0
- package/lib/types/platform.d.ts +10 -0
- package/lib/types/platform.d.ts.map +1 -0
- package/lib/types/platform.js +2 -0
- package/lib/types/wifi-module.d.ts +146 -0
- package/lib/types/wifi-module.d.ts.map +1 -0
- package/lib/types/wifi-module.js +6 -0
- package/package.json +42 -0
- package/src/modules/android/build.gradle +64 -0
- package/src/modules/android/src/main/AndroidManifest.xml +28 -0
- package/src/modules/android/src/main/java/expo/modules/customwifi/WifiModule.kt +517 -0
- package/src/modules/android/src/main/java/expo/modules/customwifi/WifiStateReceiver.kt +46 -0
- package/src/modules/expo-module.config.json +9 -0
- package/src/modules/index.ts +166 -0
- package/src/modules/ios/WifiModule.swift +399 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WiFi Bridge Handlers
|
|
4
|
+
* WebView에서 호출 가능한 WiFi 관련 핸들러 등록
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.registerWifiHandlers = registerWifiHandlers;
|
|
41
|
+
const WifiModule = __importStar(require("../modules"));
|
|
42
|
+
/**
|
|
43
|
+
* WiFi 브릿지 핸들러 등록
|
|
44
|
+
* @param config 브릿지 설정 (bridge 인스턴스, platform 정보)
|
|
45
|
+
*/
|
|
46
|
+
function registerWifiHandlers(config) {
|
|
47
|
+
const { bridge, platform } = config;
|
|
48
|
+
// 지원 플랫폼 확인
|
|
49
|
+
if (platform.OS !== 'android' && platform.OS !== 'ios') {
|
|
50
|
+
console.warn('[WiFi Bridge] WiFi module only supports Android and iOS');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 현재 WiFi 정보 조회
|
|
55
|
+
* Request: { }
|
|
56
|
+
* Response: WifiInfoResult
|
|
57
|
+
*/
|
|
58
|
+
bridge.registerHandler('getCurrentWifiInfo', async () => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await WifiModule.getCurrentWifiInfo();
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
67
|
+
errorCode: 'UNKNOWN',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* WiFi 목록 스캔 (Android only)
|
|
73
|
+
* Request: { }
|
|
74
|
+
* Response: WifiListResult
|
|
75
|
+
*/
|
|
76
|
+
bridge.registerHandler('getWifiList', async () => {
|
|
77
|
+
try {
|
|
78
|
+
const result = await WifiModule.getWifiList();
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
85
|
+
errorCode: 'UNKNOWN',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* WiFi 활성화 상태 확인
|
|
91
|
+
* Request: { }
|
|
92
|
+
* Response: WifiStateResult
|
|
93
|
+
*/
|
|
94
|
+
bridge.registerHandler('isWifiEnabled', async () => {
|
|
95
|
+
try {
|
|
96
|
+
const result = await WifiModule.isWifiEnabled();
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
103
|
+
errorCode: 'UNKNOWN',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* WiFi 연결
|
|
109
|
+
* Request: ConnectOptions { ssid, password?, security?, isHidden?, timeout? }
|
|
110
|
+
* Response: ConnectResult
|
|
111
|
+
*/
|
|
112
|
+
bridge.registerHandler('connectToWifi', async (options) => {
|
|
113
|
+
try {
|
|
114
|
+
// 유효성 검사
|
|
115
|
+
if (!options || !options.ssid) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: 'SSID is required',
|
|
119
|
+
errorCode: 'INVALID_SSID',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const connectOptions = {
|
|
123
|
+
ssid: options.ssid,
|
|
124
|
+
password: options.password,
|
|
125
|
+
security: options.security,
|
|
126
|
+
isHidden: options.isHidden,
|
|
127
|
+
timeout: options.timeout,
|
|
128
|
+
};
|
|
129
|
+
const result = await WifiModule.connectToWifi(connectOptions);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
136
|
+
errorCode: 'UNKNOWN',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* WiFi 연결 해제
|
|
142
|
+
* Request: { }
|
|
143
|
+
* Response: WifiResult
|
|
144
|
+
*/
|
|
145
|
+
bridge.registerHandler('disconnect', async () => {
|
|
146
|
+
try {
|
|
147
|
+
const result = await WifiModule.disconnect();
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
154
|
+
errorCode: 'UNKNOWN',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* 권한 확인
|
|
160
|
+
* Request: { }
|
|
161
|
+
* Response: PermissionStatus
|
|
162
|
+
*/
|
|
163
|
+
bridge.registerHandler('checkWifiPermission', async () => {
|
|
164
|
+
try {
|
|
165
|
+
const result = await WifiModule.checkPermission();
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return {
|
|
170
|
+
locationGranted: false,
|
|
171
|
+
canAccessWifiInfo: false,
|
|
172
|
+
requiredPermissions: [],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
/**
|
|
177
|
+
* 권한 요청
|
|
178
|
+
* Request: { }
|
|
179
|
+
* Response: PermissionStatus
|
|
180
|
+
*/
|
|
181
|
+
bridge.registerHandler('requestWifiPermission', async () => {
|
|
182
|
+
try {
|
|
183
|
+
const result = await WifiModule.requestPermission();
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
locationGranted: false,
|
|
189
|
+
canAccessWifiInfo: false,
|
|
190
|
+
requiredPermissions: [],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
/**
|
|
195
|
+
* WiFi 상태 변경 이벤트 리스너 등록
|
|
196
|
+
* 이벤트: wifi:stateChanged
|
|
197
|
+
*/
|
|
198
|
+
WifiModule.addWifiStateListener((event) => {
|
|
199
|
+
bridge.sendToWeb('onWifiStateChange', event);
|
|
200
|
+
});
|
|
201
|
+
console.log('[WiFi Bridge] Handlers registered successfully');
|
|
202
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WiFi Module (Cross-Platform)
|
|
3
|
+
* WiFi 정보 조회 및 연결 관리 기능 제공
|
|
4
|
+
* Supports: Android, iOS
|
|
5
|
+
*/
|
|
6
|
+
import type { WifiInfoResult, WifiListResult, WifiStateResult, ConnectOptions, ConnectResult, WifiResult, PermissionStatus, WifiStateChangeEvent } from '../types/wifi-module';
|
|
7
|
+
/**
|
|
8
|
+
* 현재 연결된 WiFi 정보 조회
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCurrentWifiInfo(): Promise<WifiInfoResult>;
|
|
11
|
+
/**
|
|
12
|
+
* 사용 가능한 WiFi 목록 스캔 (Android only)
|
|
13
|
+
*/
|
|
14
|
+
export declare function getWifiList(): Promise<WifiListResult>;
|
|
15
|
+
/**
|
|
16
|
+
* WiFi 활성화 상태 확인
|
|
17
|
+
*/
|
|
18
|
+
export declare function isWifiEnabled(): Promise<WifiStateResult>;
|
|
19
|
+
/**
|
|
20
|
+
* WiFi 네트워크에 연결
|
|
21
|
+
*/
|
|
22
|
+
export declare function connectToWifi(options: ConnectOptions): Promise<ConnectResult>;
|
|
23
|
+
/**
|
|
24
|
+
* WiFi 연결 해제
|
|
25
|
+
*/
|
|
26
|
+
export declare function disconnect(): Promise<WifiResult>;
|
|
27
|
+
/**
|
|
28
|
+
* WiFi 권한 확인
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkPermission(): Promise<PermissionStatus>;
|
|
31
|
+
/**
|
|
32
|
+
* WiFi 권한 요청
|
|
33
|
+
*/
|
|
34
|
+
export declare function requestPermission(): Promise<PermissionStatus>;
|
|
35
|
+
/**
|
|
36
|
+
* WiFi 상태 변경 이벤트 리스너 등록
|
|
37
|
+
*/
|
|
38
|
+
export declare function addWifiStateListener(listener: (event: WifiStateChangeEvent) => void): {
|
|
39
|
+
remove: () => void;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/modules/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAuB9B;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,cAAc,CAAC,CAUlE;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,cAAc,CAAC,CAmB3D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,CAAC,CAU9D;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAUnF;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAUtD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAUjE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAUnE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAC9C;IAAE,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CAMxB"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WiFi Module (Cross-Platform)
|
|
4
|
+
* WiFi 정보 조회 및 연결 관리 기능 제공
|
|
5
|
+
* Supports: Android, iOS
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getCurrentWifiInfo = getCurrentWifiInfo;
|
|
9
|
+
exports.getWifiList = getWifiList;
|
|
10
|
+
exports.isWifiEnabled = isWifiEnabled;
|
|
11
|
+
exports.connectToWifi = connectToWifi;
|
|
12
|
+
exports.disconnect = disconnect;
|
|
13
|
+
exports.checkPermission = checkPermission;
|
|
14
|
+
exports.requestPermission = requestPermission;
|
|
15
|
+
exports.addWifiStateListener = addWifiStateListener;
|
|
16
|
+
const expo_modules_core_1 = require("expo-modules-core");
|
|
17
|
+
const react_native_1 = require("react-native");
|
|
18
|
+
// Lazy 모듈 로드 (크래시 방지)
|
|
19
|
+
let WifiModule = null;
|
|
20
|
+
function getWifiModule() {
|
|
21
|
+
if (react_native_1.Platform.OS !== 'android' && react_native_1.Platform.OS !== 'ios') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (WifiModule === null) {
|
|
25
|
+
try {
|
|
26
|
+
WifiModule = (0, expo_modules_core_1.requireNativeModule)('CustomWifi');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('[CustomWifi] Failed to load native module:', error);
|
|
30
|
+
WifiModule = undefined;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return WifiModule === undefined ? null : WifiModule;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 현재 연결된 WiFi 정보 조회
|
|
38
|
+
*/
|
|
39
|
+
async function getCurrentWifiInfo() {
|
|
40
|
+
const module = getWifiModule();
|
|
41
|
+
if (!module) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: 'Module not available',
|
|
45
|
+
errorCode: 'MODULE_NOT_AVAILABLE',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return await module.getCurrentWifiInfo();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 사용 가능한 WiFi 목록 스캔 (Android only)
|
|
52
|
+
*/
|
|
53
|
+
async function getWifiList() {
|
|
54
|
+
const module = getWifiModule();
|
|
55
|
+
if (!module) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: 'Module not available',
|
|
59
|
+
errorCode: 'MODULE_NOT_AVAILABLE',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: 'WiFi scanning is not supported on iOS',
|
|
66
|
+
errorCode: 'NOT_SUPPORTED',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return await module.getWifiList();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* WiFi 활성화 상태 확인
|
|
73
|
+
*/
|
|
74
|
+
async function isWifiEnabled() {
|
|
75
|
+
const module = getWifiModule();
|
|
76
|
+
if (!module) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'Module not available',
|
|
80
|
+
errorCode: 'MODULE_NOT_AVAILABLE',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return await module.isWifiEnabled();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* WiFi 네트워크에 연결
|
|
87
|
+
*/
|
|
88
|
+
async function connectToWifi(options) {
|
|
89
|
+
const module = getWifiModule();
|
|
90
|
+
if (!module) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: 'Module not available',
|
|
94
|
+
errorCode: 'MODULE_NOT_AVAILABLE',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return await module.connectToWifi(options);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* WiFi 연결 해제
|
|
101
|
+
*/
|
|
102
|
+
async function disconnect() {
|
|
103
|
+
const module = getWifiModule();
|
|
104
|
+
if (!module) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: 'Module not available',
|
|
108
|
+
errorCode: 'MODULE_NOT_AVAILABLE',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return await module.disconnect();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* WiFi 권한 확인
|
|
115
|
+
*/
|
|
116
|
+
async function checkPermission() {
|
|
117
|
+
const module = getWifiModule();
|
|
118
|
+
if (!module) {
|
|
119
|
+
return {
|
|
120
|
+
locationGranted: false,
|
|
121
|
+
canAccessWifiInfo: false,
|
|
122
|
+
requiredPermissions: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return await module.checkPermission();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* WiFi 권한 요청
|
|
129
|
+
*/
|
|
130
|
+
async function requestPermission() {
|
|
131
|
+
const module = getWifiModule();
|
|
132
|
+
if (!module) {
|
|
133
|
+
return {
|
|
134
|
+
locationGranted: false,
|
|
135
|
+
canAccessWifiInfo: false,
|
|
136
|
+
requiredPermissions: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return await module.requestPermission();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* WiFi 상태 변경 이벤트 리스너 등록
|
|
143
|
+
*/
|
|
144
|
+
function addWifiStateListener(listener) {
|
|
145
|
+
const module = getWifiModule();
|
|
146
|
+
if (!module || !module.addListener) {
|
|
147
|
+
return { remove: () => { } };
|
|
148
|
+
}
|
|
149
|
+
return module.addListener('onWifiStateChange', listener);
|
|
150
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background 모듈 타입 정의
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 트리거 타입
|
|
6
|
+
* - interval: 주기적 실행
|
|
7
|
+
* - network_change: 네트워크 상태 변경 (연결/해제)
|
|
8
|
+
* - location_change: 위치 변경 (significant location change)
|
|
9
|
+
* - time_trigger: 예약된 시간에 실행
|
|
10
|
+
* - battery_low: 배터리 부족 (15% 이하)
|
|
11
|
+
* - battery_okay: 배터리 정상 복귀
|
|
12
|
+
* - battery_charging: 충전 시작
|
|
13
|
+
* - battery_discharging: 충전 해제
|
|
14
|
+
* - app_foreground: 앱이 포그라운드로 전환
|
|
15
|
+
* - app_background: 앱이 백그라운드로 전환
|
|
16
|
+
* - app_terminate: 앱 종료 시
|
|
17
|
+
* - custom: 사용자 정의 트리거
|
|
18
|
+
*/
|
|
19
|
+
export type TriggerType = 'interval' | 'network_change' | 'location_change' | 'time_trigger' | 'battery_low' | 'battery_okay' | 'battery_charging' | 'battery_discharging' | 'app_foreground' | 'app_background' | 'app_terminate' | 'custom';
|
|
20
|
+
/**
|
|
21
|
+
* 트리거 설정 (triggers 배열에서 사용)
|
|
22
|
+
* 문자열 또는 상세 설정 객체
|
|
23
|
+
*/
|
|
24
|
+
export type TriggerConfig = TriggerType | {
|
|
25
|
+
/** 트리거 타입 */
|
|
26
|
+
type: TriggerType;
|
|
27
|
+
/** custom 트리거의 식별자 */
|
|
28
|
+
customId?: string;
|
|
29
|
+
/** 트리거별 추가 옵션 */
|
|
30
|
+
options?: {
|
|
31
|
+
/** battery_low: 임계값 % (기본 15) */
|
|
32
|
+
threshold?: number;
|
|
33
|
+
/** location_change: 최소 이동 거리(m) */
|
|
34
|
+
minDistance?: number;
|
|
35
|
+
/** network_change: 특정 타입만 감지 */
|
|
36
|
+
networkTypes?: Array<'wifi' | 'cellular' | 'ethernet'>;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* 작업 이벤트
|
|
41
|
+
*/
|
|
42
|
+
export interface TaskEvent {
|
|
43
|
+
/** 작업 ID */
|
|
44
|
+
taskId: string;
|
|
45
|
+
/** 등록 시 지정한 콜백 ID */
|
|
46
|
+
callbackId?: string;
|
|
47
|
+
/** 이벤트 타입 */
|
|
48
|
+
type: 'started' | 'stopped' | 'restart' | 'error' | 'trigger' | 'action';
|
|
49
|
+
/** 트리거 종류 (type이 'trigger'일 때) */
|
|
50
|
+
trigger?: TriggerType;
|
|
51
|
+
/** custom 트리거 식별자 */
|
|
52
|
+
customTriggerId?: string;
|
|
53
|
+
/** 알림 액션 버튼 ID (type이 'action'일 때) */
|
|
54
|
+
actionId?: string;
|
|
55
|
+
/** 에러 메시지 (type이 'error'일 때) */
|
|
56
|
+
error?: string;
|
|
57
|
+
/** 트리거 관련 데이터 */
|
|
58
|
+
data?: {
|
|
59
|
+
/** 배터리 레벨 (%) */
|
|
60
|
+
batteryLevel?: number;
|
|
61
|
+
/** 네트워크 타입 */
|
|
62
|
+
networkType?: 'wifi' | 'cellular' | 'ethernet' | 'none';
|
|
63
|
+
/** 네트워크 연결 상태 */
|
|
64
|
+
isConnected?: boolean;
|
|
65
|
+
/** 위치 정보 */
|
|
66
|
+
location?: {
|
|
67
|
+
latitude: number;
|
|
68
|
+
longitude: number;
|
|
69
|
+
};
|
|
70
|
+
/** 기타 커스텀 데이터 */
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
};
|
|
73
|
+
/** 타임스탬프 */
|
|
74
|
+
timestamp: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 작업 이벤트 콜백 함수 타입
|
|
78
|
+
*/
|
|
79
|
+
export type TaskCallback = (event: TaskEvent) => void | Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* 액션 버튼 콜백 함수 타입
|
|
82
|
+
*/
|
|
83
|
+
export type ActionCallback = (actionId: string, taskId: string) => void | Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* 백그라운드 작업 설정
|
|
86
|
+
*/
|
|
87
|
+
export interface BackgroundTask {
|
|
88
|
+
/** 작업 고유 ID (WebView에서 직접 지정) */
|
|
89
|
+
taskId: string;
|
|
90
|
+
/**
|
|
91
|
+
* 실행 모드
|
|
92
|
+
* - persistent: 포그라운드 서비스로 항상 실행 (Android), 지속적 백그라운드 (iOS)
|
|
93
|
+
* - efficient: 시스템이 관리하는 효율적 실행 (WorkManager/BGTaskScheduler)
|
|
94
|
+
*/
|
|
95
|
+
mode: 'persistent' | 'efficient';
|
|
96
|
+
/**
|
|
97
|
+
* 간격 기반 실행 (밀리초)
|
|
98
|
+
* - 0 또는 미지정: interval 트리거 비활성화
|
|
99
|
+
* - persistent 모드: 최소 1000ms (1초)
|
|
100
|
+
* - efficient 모드: 최소 900000ms (15분, 시스템 제한)
|
|
101
|
+
*/
|
|
102
|
+
interval?: number;
|
|
103
|
+
/**
|
|
104
|
+
* 이벤트 트리거 목록
|
|
105
|
+
* 문자열 또는 상세 설정 객체 배열
|
|
106
|
+
*/
|
|
107
|
+
triggers?: TriggerConfig[];
|
|
108
|
+
/**
|
|
109
|
+
* 예약 실행 시간 (Unix timestamp, ms)
|
|
110
|
+
* time_trigger 사용 시 필요
|
|
111
|
+
*/
|
|
112
|
+
scheduledTime?: number;
|
|
113
|
+
/**
|
|
114
|
+
* 콜백 식별자
|
|
115
|
+
* 여러 작업의 이벤트를 구분하기 위한 ID
|
|
116
|
+
*/
|
|
117
|
+
callbackId?: string;
|
|
118
|
+
/** 초기 알림 설정 (persistent 모드 필수) */
|
|
119
|
+
notification?: NotificationConfig;
|
|
120
|
+
/**
|
|
121
|
+
* 이벤트 발생 시 실행할 콜백 함수
|
|
122
|
+
* 모든 이벤트 타입에 대해 호출됨
|
|
123
|
+
*/
|
|
124
|
+
callback?: TaskCallback;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 알림 액션 버튼
|
|
128
|
+
*/
|
|
129
|
+
export interface NotificationAction {
|
|
130
|
+
/** 액션 고유 식별자 */
|
|
131
|
+
id: string;
|
|
132
|
+
/** 버튼에 표시될 텍스트 */
|
|
133
|
+
title: string;
|
|
134
|
+
/** 버튼 아이콘 리소스명 (Android only) */
|
|
135
|
+
icon?: string;
|
|
136
|
+
/**
|
|
137
|
+
* 버튼 클릭 시 실행될 콜백 함수
|
|
138
|
+
* 지정하지 않으면 onTaskEvent로 'action' 이벤트 전달
|
|
139
|
+
*/
|
|
140
|
+
onPress?: ActionCallback;
|
|
141
|
+
/**
|
|
142
|
+
* 버튼 클릭 시 알림 자동 닫기 여부
|
|
143
|
+
* @default true
|
|
144
|
+
*/
|
|
145
|
+
dismissOnPress?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* 버튼 클릭 시 앱을 포그라운드로 가져올지 여부
|
|
148
|
+
* @default false
|
|
149
|
+
*/
|
|
150
|
+
bringToForeground?: boolean;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 알림 진행 상태바
|
|
154
|
+
*/
|
|
155
|
+
export interface NotificationProgress {
|
|
156
|
+
/** 현재 진행 값 */
|
|
157
|
+
current: number;
|
|
158
|
+
/** 최대 값 */
|
|
159
|
+
max: number;
|
|
160
|
+
/**
|
|
161
|
+
* 무한 진행 표시 (indeterminate progress bar)
|
|
162
|
+
* true면 current/max 무시
|
|
163
|
+
*/
|
|
164
|
+
indeterminate?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 알림 설정
|
|
168
|
+
*/
|
|
169
|
+
export interface NotificationConfig {
|
|
170
|
+
/** 대상 작업 ID (updateNotification 시 필요) */
|
|
171
|
+
taskId?: string;
|
|
172
|
+
/** 알림 제목 */
|
|
173
|
+
title: string;
|
|
174
|
+
/** 알림 본문 */
|
|
175
|
+
body: string;
|
|
176
|
+
/**
|
|
177
|
+
* 알림 아이콘 리소스명 (Android)
|
|
178
|
+
* @example 'ic_notification' → res/drawable/ic_notification.png
|
|
179
|
+
*/
|
|
180
|
+
icon?: string;
|
|
181
|
+
/**
|
|
182
|
+
* 아이콘/강조 색상 (hex)
|
|
183
|
+
* @example '#4CAF50', '#FF5733'
|
|
184
|
+
*/
|
|
185
|
+
color?: string;
|
|
186
|
+
/**
|
|
187
|
+
* 알림 우선순위
|
|
188
|
+
* - min: 최소 (무음, 상태바에만 표시)
|
|
189
|
+
* - low: 낮음 (무음)
|
|
190
|
+
* - default: 기본
|
|
191
|
+
* - high: 높음 (헤드업 알림)
|
|
192
|
+
* - max: 최대 (긴급, 헤드업 알림)
|
|
193
|
+
*/
|
|
194
|
+
priority?: 'min' | 'low' | 'default' | 'high' | 'max';
|
|
195
|
+
/**
|
|
196
|
+
* 지속 알림 여부
|
|
197
|
+
* true면 사용자가 스와이프로 닫을 수 없음
|
|
198
|
+
* persistent 모드에서는 항상 true로 강제됨
|
|
199
|
+
*/
|
|
200
|
+
ongoing?: boolean;
|
|
201
|
+
/** 진행 상태바 설정 */
|
|
202
|
+
progress?: NotificationProgress;
|
|
203
|
+
/**
|
|
204
|
+
* 알림 액션 버튼 목록
|
|
205
|
+
* 최대 3개까지 지원
|
|
206
|
+
*/
|
|
207
|
+
actions?: NotificationAction[];
|
|
208
|
+
/**
|
|
209
|
+
* 알림 채널 ID (Android 8.0+)
|
|
210
|
+
* 미지정 시 기본 채널 사용
|
|
211
|
+
*/
|
|
212
|
+
channelId?: string;
|
|
213
|
+
/**
|
|
214
|
+
* 알림 채널 이름 (Android 8.0+)
|
|
215
|
+
* 채널 생성 시 사용자에게 표시되는 이름
|
|
216
|
+
*/
|
|
217
|
+
channelName?: string;
|
|
218
|
+
/**
|
|
219
|
+
* 알림 채널 설명 (Android 8.0+)
|
|
220
|
+
*/
|
|
221
|
+
channelDescription?: string;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 개별 작업 상태
|
|
225
|
+
*/
|
|
226
|
+
export interface TaskStatus {
|
|
227
|
+
/** 작업 ID */
|
|
228
|
+
taskId: string;
|
|
229
|
+
/** 실행 중 여부 */
|
|
230
|
+
isRunning: boolean;
|
|
231
|
+
/** 실행 모드 */
|
|
232
|
+
mode: 'persistent' | 'efficient';
|
|
233
|
+
/** 시작 시간 (Unix timestamp, ms) */
|
|
234
|
+
startedAt?: number;
|
|
235
|
+
/** 마지막 트리거 시간 */
|
|
236
|
+
lastTriggeredAt?: number;
|
|
237
|
+
/** 총 트리거 횟수 */
|
|
238
|
+
triggerCount?: number;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 전체 백그라운드 상태
|
|
242
|
+
*/
|
|
243
|
+
export interface BackgroundStatus {
|
|
244
|
+
/** 등록된 작업 목록 */
|
|
245
|
+
tasks: TaskStatus[];
|
|
246
|
+
/** 실행 중인 작업이 있는지 여부 */
|
|
247
|
+
isAnyRunning: boolean;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 권한 상태
|
|
251
|
+
*/
|
|
252
|
+
export interface PermissionStatus {
|
|
253
|
+
/** 백그라운드 실행 가능 여부 */
|
|
254
|
+
canRunBackground: boolean;
|
|
255
|
+
/** 배터리 최적화 예외 여부 (Android) */
|
|
256
|
+
batteryOptimizationExempt?: boolean;
|
|
257
|
+
/** 필요한 권한 목록 */
|
|
258
|
+
requiredPermissions: string[];
|
|
259
|
+
/** 거부된 권한 목록 */
|
|
260
|
+
deniedPermissions?: string[];
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* 백그라운드 에러 타입
|
|
264
|
+
*/
|
|
265
|
+
export type BackgroundError = 'TASK_NOT_FOUND' | 'TASK_ALREADY_EXISTS' | 'TASK_ALREADY_RUNNING' | 'TASK_NOT_RUNNING' | 'PERMISSION_DENIED' | 'SYSTEM_RESTRICTED' | 'WEBVIEW_INIT_FAILED' | 'INVALID_INPUT' | 'INVALID_INTERVAL' | 'INVALID_TRIGGER' | 'NOTIFICATION_REQUIRED' | 'UNKNOWN';
|
|
266
|
+
/**
|
|
267
|
+
* 작업 등록 결과
|
|
268
|
+
*/
|
|
269
|
+
export interface RegisterTaskResult {
|
|
270
|
+
success: boolean;
|
|
271
|
+
taskId?: string;
|
|
272
|
+
error?: BackgroundError;
|
|
273
|
+
message?: string;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 작업 시작/중지 결과
|
|
277
|
+
*/
|
|
278
|
+
export interface TaskActionResult {
|
|
279
|
+
success: boolean;
|
|
280
|
+
taskId?: string;
|
|
281
|
+
error?: BackgroundError;
|
|
282
|
+
message?: string;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* 알림 업데이트 결과
|
|
286
|
+
*/
|
|
287
|
+
export interface NotificationResult {
|
|
288
|
+
success: boolean;
|
|
289
|
+
error?: string;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=background-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-module.d.ts","sourceRoot":"","sources":["../../src/types/background-module.ts"],"names":[],"mappings":"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"}
|