15 KiB
15 KiB
웹 페이지 통합 가이드
이 문서는 https://logistream.kpslp.kr 웹사이트에서 모바일 앱과 통신하는 방법을 설명합니다.
개요
모바일 앱은 웹뷰를 통해 웹사이트를 표시하며, JavaScript 브릿지를 통해 양방향 통신이 가능합니다.
1. 네이티브 기능 사용하기
1.1 로그인 성공 알림
사용자가 로그인에 성공하면 다음 함수를 호출하여 앱에 알립니다:
// 로그인 API 응답 후
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username, password })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 네이티브 앱에 로그인 성공 알림
if (window.notifyLoginSuccess) {
window.notifyLoginSuccess({
userId: data.user.id,
userName: data.user.name,
userEmail: data.user.email,
token: data.token // JWT 토큰
});
}
// 위치 추적 자동 시작
if (window.startLocationTracking) {
window.startLocationTracking();
}
}
});
1.2 로그아웃 알림
function handleLogout() {
// 로그아웃 처리
localStorage.clear();
sessionStorage.clear();
// 네이티브 앱에 로그아웃 알림
if (window.notifyLogout) {
window.notifyLogout();
}
// 로그인 페이지로 리다이렉트
window.location.href = '/login';
}
1.3 현재 위치 요청
// 현재 위치가 필요한 경우
function getCurrentLocation() {
return new Promise((resolve, reject) => {
// 네이티브 메시지 리스너 등록
const handler = (event) => {
if (event.detail.type === 'LOCATION_UPDATE') {
window.removeEventListener('nativeMessage', handler);
resolve(event.detail.location);
}
};
window.addEventListener('nativeMessage', handler);
// 위치 요청
if (window.requestCurrentLocation) {
window.requestCurrentLocation();
} else {
// 웹 브라우저에서 실행 중인 경우 fallback
navigator.geolocation.getCurrentPosition(
(position) => {
window.removeEventListener('nativeMessage', handler);
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy
});
},
(error) => {
window.removeEventListener('nativeMessage', handler);
reject(error);
}
);
}
// 타임아웃 설정 (5초)
setTimeout(() => {
window.removeEventListener('nativeMessage', handler);
reject(new Error('위치 요청 타임아웃'));
}, 5000);
});
}
// 사용 예시
async function showCurrentLocation() {
try {
const location = await getCurrentLocation();
console.log('현재 위치:', location);
// 지도에 표시 등
displayOnMap(location.latitude, location.longitude);
} catch (error) {
console.error('위치 가져오기 실패:', error);
}
}
1.4 위치 추적 제어
// 위치 추적 시작
function startTracking() {
if (window.startLocationTracking) {
window.startLocationTracking();
console.log('위치 추적 시작');
}
}
// 위치 추적 중지
function stopTracking() {
if (window.stopLocationTracking) {
window.stopLocationTracking();
console.log('위치 추적 중지');
}
}
// UI 토글 버튼 예시
const trackingButton = document.getElementById('tracking-toggle');
let isTracking = false;
trackingButton.addEventListener('click', () => {
if (isTracking) {
stopTracking();
trackingButton.textContent = '추적 시작';
} else {
startTracking();
trackingButton.textContent = '추적 중지';
}
isTracking = !isTracking;
});
2. 네이티브 메시지 수신
2.1 메시지 리스너 설정
// 페이지 로드 시 리스너 등록
window.addEventListener('nativeMessage', function(event) {
const data = event.detail;
console.log('네이티브 메시지 수신:', data);
switch(data.type) {
case 'NATIVE_INFO':
handleNativeInfo(data);
break;
case 'LOCATION_UPDATE':
handleLocationUpdate(data.location);
break;
default:
console.log('알 수 없는 메시지 타입:', data.type);
}
});
2.2 네이티브 정보 처리
function handleNativeInfo(data) {
console.log('플랫폼:', data.platform); // 'android' 또는 'ios'
console.log('버전:', data.version);
console.log('위치 권한:', data.hasLocationPermission);
// 플랫폼별 UI 조정
if (data.platform === 'ios') {
document.body.classList.add('platform-ios');
} else if (data.platform === 'android') {
document.body.classList.add('platform-android');
}
// 위치 권한이 없는 경우 안내
if (!data.hasLocationPermission) {
showAlert('위치 권한이 필요합니다. 설정에서 권한을 허용해주세요.');
}
}
2.3 위치 업데이트 처리
function handleLocationUpdate(location) {
console.log('위치 업데이트:', location);
// 지도에 현재 위치 표시
updateMapMarker(location.latitude, location.longitude);
// UI 업데이트
document.getElementById('latitude').textContent = location.latitude.toFixed(6);
document.getElementById('longitude').textContent = location.longitude.toFixed(6);
document.getElementById('accuracy').textContent = location.accuracy.toFixed(2) + 'm';
if (location.speed !== null) {
document.getElementById('speed').textContent = (location.speed * 3.6).toFixed(1) + 'km/h';
}
}
3. 환경 감지
3.1 모바일 앱 환경 확인
// 모바일 앱에서 실행 중인지 확인
function isRunningInApp() {
return typeof window.ReactNativeWebView !== 'undefined';
}
// 사용 예시
if (isRunningInApp()) {
console.log('모바일 앱에서 실행 중');
// 앱 전용 기능 활성화
enableAppFeatures();
} else {
console.log('웹 브라우저에서 실행 중');
// 웹 전용 기능 활성화
enableWebFeatures();
}
3.2 플랫폼별 처리
let currentPlatform = 'web';
window.addEventListener('nativeMessage', function(event) {
if (event.detail.type === 'NATIVE_INFO') {
currentPlatform = event.detail.platform;
}
});
function getPlatform() {
return currentPlatform;
}
// 사용 예시
if (getPlatform() === 'ios') {
// iOS 전용 스타일 적용
applyIOSStyles();
} else if (getPlatform() === 'android') {
// Android 전용 스타일 적용
applyAndroidStyles();
}
4. 완전한 통합 예제
4.1 로그인 페이지
<!DOCTYPE html>
<html>
<head>
<title>로그인</title>
</head>
<body>
<form id="login-form">
<input type="text" id="username" placeholder="아이디" required>
<input type="password" id="password" placeholder="비밀번호" required>
<button type="submit">로그인</button>
</form>
<script>
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
// 로컬 스토리지에 저장
localStorage.setItem('user', JSON.stringify(data.user));
localStorage.setItem('token', data.token);
// 네이티브 앱에 알림
if (window.notifyLoginSuccess) {
window.notifyLoginSuccess({
userId: data.user.id,
userName: data.user.name,
userEmail: data.user.email,
token: data.token
});
}
// 위치 추적 시작
if (window.startLocationTracking) {
window.startLocationTracking();
}
// 메인 페이지로 이동
window.location.href = '/dashboard';
} else {
alert('로그인 실패: ' + data.message);
}
} catch (error) {
console.error('로그인 오류:', error);
alert('로그인 중 오류가 발생했습니다.');
}
});
</script>
</body>
</html>
4.2 대시보드 페이지
<!DOCTYPE html>
<html>
<head>
<title>차량 위치 추적</title>
<style>
.location-info {
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
margin: 10px 0;
}
.tracking-controls {
margin: 20px 0;
}
.btn {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
</style>
</head>
<body>
<h1>차량 위치 추적 시스템</h1>
<div class="location-info">
<h2>현재 위치</h2>
<p>위도: <span id="latitude">-</span></p>
<p>경도: <span id="longitude">-</span></p>
<p>정확도: <span id="accuracy">-</span></p>
<p>속도: <span id="speed">-</span></p>
</div>
<div class="tracking-controls">
<button id="get-location" class="btn btn-primary">현재 위치 가져오기</button>
<button id="start-tracking" class="btn btn-primary">추적 시작</button>
<button id="stop-tracking" class="btn btn-danger">추적 중지</button>
<button id="logout" class="btn btn-danger">로그아웃</button>
</div>
<script>
// 네이티브 메시지 리스너
window.addEventListener('nativeMessage', function(event) {
const data = event.detail;
if (data.type === 'LOCATION_UPDATE') {
updateLocationDisplay(data.location);
} else if (data.type === 'NATIVE_INFO') {
console.log('앱 정보:', data);
}
});
// 위치 정보 표시 업데이트
function updateLocationDisplay(location) {
document.getElementById('latitude').textContent = location.latitude.toFixed(6);
document.getElementById('longitude').textContent = location.longitude.toFixed(6);
document.getElementById('accuracy').textContent = location.accuracy.toFixed(2) + 'm';
if (location.speed !== null && location.speed > 0) {
document.getElementById('speed').textContent = (location.speed * 3.6).toFixed(1) + 'km/h';
} else {
document.getElementById('speed').textContent = '0 km/h';
}
}
// 현재 위치 가져오기 버튼
document.getElementById('get-location').addEventListener('click', () => {
if (window.requestCurrentLocation) {
window.requestCurrentLocation();
} else {
alert('네이티브 앱에서만 사용 가능합니다.');
}
});
// 추적 시작 버튼
document.getElementById('start-tracking').addEventListener('click', () => {
if (window.startLocationTracking) {
window.startLocationTracking();
alert('위치 추적을 시작했습니다.');
} else {
alert('네이티브 앱에서만 사용 가능합니다.');
}
});
// 추적 중지 버튼
document.getElementById('stop-tracking').addEventListener('click', () => {
if (window.stopLocationTracking) {
window.stopLocationTracking();
alert('위치 추적을 중지했습니다.');
} else {
alert('네이티브 앱에서만 사용 가능합니다.');
}
});
// 로그아웃 버튼
document.getElementById('logout').addEventListener('click', async () => {
try {
// 서버에 로그아웃 요청
await fetch('/api/logout', { method: 'POST' });
// 로컬 데이터 삭제
localStorage.clear();
sessionStorage.clear();
// 네이티브 앱에 알림
if (window.notifyLogout) {
window.notifyLogout();
}
// 로그인 페이지로 이동
window.location.href = '/login';
} catch (error) {
console.error('로그아웃 오류:', error);
}
});
// 페이지 로드 시 저장된 사용자 정보 확인
window.addEventListener('load', () => {
const user = localStorage.getItem('user');
if (!user) {
window.location.href = '/login';
}
});
</script>
</body>
</html>
5. 서버 API 구현 예시
5.1 위치 정보 수신 API (Node.js/Express)
// routes/location.js
const express = require('express');
const router = express.Router();
// 위치 정보 수신 엔드포인트
router.post('/api/location', async (req, res) => {
try {
const {
userId,
userName,
userEmail,
location,
deviceInfo
} = req.body;
// 데이터 검증
if (!userId || !location || !location.latitude || !location.longitude) {
return res.status(400).json({
success: false,
message: '필수 데이터가 누락되었습니다.'
});
}
// 데이터베이스에 저장
const locationRecord = await db.locations.create({
user_id: userId,
user_name: userName,
user_email: userEmail,
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
altitude: location.altitude,
heading: location.heading,
speed: location.speed,
platform: deviceInfo.platform,
platform_version: deviceInfo.version,
timestamp: new Date(location.timestamp),
created_at: new Date()
});
console.log(`위치 업데이트: ${userName} (${userId}) - ${location.latitude}, ${location.longitude}`);
res.json({
success: true,
message: '위치 정보가 저장되었습니다.',
id: locationRecord.id
});
} catch (error) {
console.error('위치 저장 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
});
module.exports = router;
6. 주의사항
- 보안: JWT 토큰은 안전하게 저장하고 전송해야 합니다.
- 배터리: 위치 추적은 배터리를 많이 소모하므로 필요할 때만 활성화하세요.
- 권한: 사용자에게 위치 권한의 필요성을 명확히 설명하세요.
- 오류 처리: 네트워크 오류나 권한 거부 상황을 적절히 처리하세요.
- Fallback: 웹 브라우저에서도 작동할 수 있도록 fallback 로직을 구현하세요.
7. 디버깅
7.1 웹뷰 디버깅 (Android)
# Chrome에서 chrome://inspect 접속
# 연결된 디바이스의 웹뷰 선택
7.2 웹뷰 디버깅 (iOS)
Safari > 개발 > [디바이스 이름] > [앱 이름]
7.3 로그 확인
// 콘솔 로그는 네이티브 디버거에서 확인 가능
console.log('디버그 메시지');
console.error('오류 메시지');