import React, { useEffect, useRef, useState } from 'react'; import { SafeAreaView, StatusBar, StyleSheet, PermissionsAndroid, Platform, Alert, AppState, } from 'react-native'; import { WebView } from 'react-native-webview'; import Geolocation from 'react-native-geolocation-service'; import AsyncStorage from '@react-native-async-storage/async-storage'; import BackgroundTimer from 'react-native-background-timer'; import axios from 'axios'; const WEBSITE_URL = 'https://logistream.kpslp.kr'; const LOCATION_UPDATE_INTERVAL = 10000; // 10초마다 위치 업데이트 const API_ENDPOINT = 'https://logistream.kpslp.kr/api/location'; // 서버 API 엔드포인트 interface LocationData { latitude: number; longitude: number; accuracy: number; altitude: number | null; heading: number | null; speed: number | null; timestamp: number; } function App(): React.JSX.Element { const webViewRef = useRef(null); const [hasLocationPermission, setHasLocationPermission] = useState(false); const [userInfo, setUserInfo] = useState(null); const locationIntervalRef = useRef(null); // 위치 권한 요청 (Android) const requestLocationPermissionAndroid = async () => { try { const granted = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION, ]); if ( granted['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED ) { setHasLocationPermission(true); return true; } else { Alert.alert('권한 필요', '위치 권한이 필요합니다.'); return false; } } catch (err) { console.warn(err); return false; } }; // 위치 권한 요청 (iOS) const requestLocationPermissionIOS = async () => { try { const result = await Geolocation.requestAuthorization('always'); if (result === 'granted' || result === 'whenInUse') { setHasLocationPermission(true); return true; } else { Alert.alert('권한 필요', '위치 권한이 필요합니다.'); return false; } } catch (error) { console.error('iOS 위치 권한 오류:', error); return false; } }; // 위치 정보 가져오기 const getCurrentLocation = (): Promise => { return new Promise((resolve, reject) => { Geolocation.getCurrentPosition( (position) => { const locationData: LocationData = { latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy, altitude: position.coords.altitude, heading: position.coords.heading, speed: position.coords.speed, timestamp: position.timestamp, }; resolve(locationData); }, (error) => { console.error('위치 정보 오류:', error); reject(error); }, { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000, showLocationDialog: true, } ); }); }; // 서버로 위치 정보 전송 const sendLocationToServer = async (locationData: LocationData) => { try { const userData = await AsyncStorage.getItem('userInfo'); const parsedUserData = userData ? JSON.parse(userData) : null; if (!parsedUserData) { console.log('사용자 정보가 없습니다. 로그인이 필요합니다.'); return; } const payload = { userId: parsedUserData.userId, userName: parsedUserData.userName, userEmail: parsedUserData.userEmail, location: locationData, deviceInfo: { platform: Platform.OS, version: Platform.Version, }, }; console.log('서버로 위치 전송:', payload); const response = await axios.post(API_ENDPOINT, payload, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${parsedUserData.token || ''}`, }, timeout: 10000, }); console.log('서버 응답:', response.data); } catch (error) { console.error('위치 전송 오류:', error); } }; // 위치 추적 시작 const startLocationTracking = () => { if (locationIntervalRef.current) { return; // 이미 실행 중 } console.log('위치 추적 시작'); // 즉시 첫 번째 위치 전송 getCurrentLocation() .then(sendLocationToServer) .catch(console.error); // 백그라운드에서도 작동하는 타이머 locationIntervalRef.current = BackgroundTimer.setInterval(() => { getCurrentLocation() .then(sendLocationToServer) .catch(console.error); }, LOCATION_UPDATE_INTERVAL); }; // 위치 추적 중지 const stopLocationTracking = () => { if (locationIntervalRef.current) { BackgroundTimer.clearInterval(locationIntervalRef.current); locationIntervalRef.current = null; console.log('위치 추적 중지'); } }; // 웹뷰에서 메시지 수신 const handleWebViewMessage = async (event: any) => { try { const data = JSON.parse(event.nativeEvent.data); console.log('웹뷰로부터 메시지 수신:', data); switch (data.type) { case 'LOGIN_SUCCESS': // 로그인 성공 시 사용자 정보 저장 및 위치 추적 시작 await AsyncStorage.setItem('userInfo', JSON.stringify(data.userInfo)); setUserInfo(data.userInfo); startLocationTracking(); break; case 'LOGOUT': // 로그아웃 시 위치 추적 중지 및 사용자 정보 삭제 stopLocationTracking(); await AsyncStorage.removeItem('userInfo'); setUserInfo(null); break; case 'REQUEST_LOCATION': // 웹에서 현재 위치 요청 const location = await getCurrentLocation(); webViewRef.current?.postMessage( JSON.stringify({ type: 'LOCATION_UPDATE', location, }) ); break; case 'START_TRACKING': // 위치 추적 시작 요청 startLocationTracking(); break; case 'STOP_TRACKING': // 위치 추적 중지 요청 stopLocationTracking(); break; default: console.log('알 수 없는 메시지 타입:', data.type); } } catch (error) { console.error('메시지 처리 오류:', error); } }; // 웹뷰에 네이티브 정보 전송 const sendNativeInfoToWeb = () => { const nativeInfo = { type: 'NATIVE_INFO', platform: Platform.OS, version: Platform.Version, hasLocationPermission, }; webViewRef.current?.postMessage(JSON.stringify(nativeInfo)); }; // 앱 초기화 useEffect(() => { const initApp = async () => { // 위치 권한 요청 if (Platform.OS === 'android') { await requestLocationPermissionAndroid(); } else { await requestLocationPermissionIOS(); } // 저장된 사용자 정보 확인 const savedUserInfo = await AsyncStorage.getItem('userInfo'); if (savedUserInfo) { const parsedUserInfo = JSON.parse(savedUserInfo); setUserInfo(parsedUserInfo); // 자동으로 위치 추적 시작 startLocationTracking(); } }; initApp(); // 앱 상태 변경 감지 const subscription = AppState.addEventListener('change', (nextAppState) => { console.log('앱 상태 변경:', nextAppState); // 앱이 포그라운드로 돌아올 때 웹뷰에 정보 전송 if (nextAppState === 'active') { sendNativeInfoToWeb(); } }); // 클린업 return () => { stopLocationTracking(); subscription.remove(); }; }, []); // 웹뷰 JavaScript 주입 코드 const injectedJavaScript = ` (function() { // 네이티브로 메시지 전송 함수 window.sendToNative = function(type, data) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: type, ...data })); }; // 네이티브로부터 메시지 수신 window.addEventListener('message', function(event) { try { const data = JSON.parse(event.data); console.log('네이티브로부터 메시지:', data); // 커스텀 이벤트 발생 const customEvent = new CustomEvent('nativeMessage', { detail: data }); window.dispatchEvent(customEvent); } catch (error) { console.error('메시지 파싱 오류:', error); } }); // 로그인 성공 시 호출할 함수 (웹 페이지에서 사용) window.notifyLoginSuccess = function(userInfo) { window.sendToNative('LOGIN_SUCCESS', { userInfo: userInfo }); }; // 로그아웃 시 호출할 함수 window.notifyLogout = function() { window.sendToNative('LOGOUT', {}); }; // 현재 위치 요청 함수 window.requestCurrentLocation = function() { window.sendToNative('REQUEST_LOCATION', {}); }; // 위치 추적 시작/중지 window.startLocationTracking = function() { window.sendToNative('START_TRACKING', {}); }; window.stopLocationTracking = function() { window.sendToNative('STOP_TRACKING', {}); }; console.log('네이티브 브릿지 초기화 완료'); })(); true; `; return ( { const { nativeEvent } = syntheticEvent; console.error('웹뷰 오류:', nativeEvent); }} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#ffffff', }, webview: { flex: 1, }, }); export default App;