네트워크 알림 오류 수정 및 리팩토링
This commit is contained in:
56
src/utils/network/checker.ts
Normal file
56
src/utils/network/checker.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 연결 확인 유틸리티
|
||||||
|
*/
|
||||||
|
import { setNetworkStatus } from './status';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 네트워크 상태 확인 (개선 버전)
|
||||||
|
*/
|
||||||
|
export const checkNetworkStatus = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
// 더 빠른 타임아웃 설정 (2초)
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
||||||
|
|
||||||
|
// 여러 엔드포인트로 확인하여 신뢰성 향상
|
||||||
|
const endpoints = [
|
||||||
|
'https://www.google.com',
|
||||||
|
'https://www.cloudflare.com',
|
||||||
|
'https://www.apple.com'
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 각 엔드포인트에 대해 fetch 요청 시도
|
||||||
|
const fetchPromises = endpoints.map(url =>
|
||||||
|
fetch(url, { method: 'HEAD', signal: controller.signal })
|
||||||
|
.then(res => {
|
||||||
|
if (res.ok) return true;
|
||||||
|
throw new Error(`Failed to fetch ${url}`);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(`[네트워크] ${url} 연결 실패:`, err.message);
|
||||||
|
return false; // 실패 시 false 반환
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 모든 요청을 병렬로 실행하고 결과 확인
|
||||||
|
const results = await Promise.all(fetchPromises);
|
||||||
|
|
||||||
|
// 하나라도 성공했으면 온라인으로 판단
|
||||||
|
const isOnline = results.some(result => result === true);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setNetworkStatus(isOnline ? 'online' : 'offline');
|
||||||
|
return isOnline;
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
console.error('[네트워크] 네트워크 확인 실패:', error);
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 연결 확인 중 예상치 못한 오류:', error);
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
42
src/utils/network/index.ts
Normal file
42
src/utils/network/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 유틸리티 모듈
|
||||||
|
*
|
||||||
|
* 네트워크 상태 관리, 오류 처리, 재시도 로직 등을 제공합니다.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 타입 내보내기
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
// 네트워크 상태 관리
|
||||||
|
export {
|
||||||
|
setNetworkStatus,
|
||||||
|
getNetworkStatus,
|
||||||
|
onNetworkStatusChange
|
||||||
|
} from './status';
|
||||||
|
|
||||||
|
// 네트워크 연결 확인
|
||||||
|
export {
|
||||||
|
checkNetworkStatus
|
||||||
|
} from './checker';
|
||||||
|
|
||||||
|
// 네트워크 모니터링
|
||||||
|
export {
|
||||||
|
startNetworkMonitoring,
|
||||||
|
stopNetworkMonitoring,
|
||||||
|
attemptReconnect
|
||||||
|
} from './monitor';
|
||||||
|
|
||||||
|
// 동기화 작업 큐 관리
|
||||||
|
export {
|
||||||
|
addToSyncQueue,
|
||||||
|
getSyncQueue,
|
||||||
|
removeFromSyncQueue,
|
||||||
|
updateSyncQueueItem,
|
||||||
|
processPendingSyncQueue
|
||||||
|
} from './queue';
|
||||||
|
|
||||||
|
// 재시도 로직
|
||||||
|
export {
|
||||||
|
withRetry,
|
||||||
|
withTimeout
|
||||||
|
} from './retry';
|
||||||
181
src/utils/network/monitor.ts
Normal file
181
src/utils/network/monitor.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 모니터링 유틸리티
|
||||||
|
*/
|
||||||
|
import { NetworkEventHandlers, ExtendedWindow } from './types';
|
||||||
|
import { setNetworkStatus, getNetworkStatus } from './status';
|
||||||
|
import { checkNetworkStatus } from './checker';
|
||||||
|
import { processPendingSyncQueue } from './queue';
|
||||||
|
|
||||||
|
// 네트워크 모니터링 상태
|
||||||
|
let isMonitoring = false;
|
||||||
|
let networkCheckInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
|
// 네트워크 재연결 시도 설정
|
||||||
|
const RECONNECT_INTERVAL = 5000; // 5초마다 재연결 시도
|
||||||
|
const MAX_RECONNECT_ATTEMPTS = 10; // 최대 재연결 시도 횟수
|
||||||
|
let reconnectAttempts = 0;
|
||||||
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 재연결 시도
|
||||||
|
*/
|
||||||
|
export const attemptReconnect = async (): Promise<boolean> => {
|
||||||
|
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
||||||
|
console.log(`[네트워크] 최대 재연결 시도 횟수(${MAX_RECONNECT_ATTEMPTS}회) 도달`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectAttempts++;
|
||||||
|
console.log(`[네트워크] 재연결 시도 ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`);
|
||||||
|
|
||||||
|
// 재연결 상태로 변경
|
||||||
|
setNetworkStatus('reconnecting');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isOnline = await checkNetworkStatus();
|
||||||
|
|
||||||
|
if (isOnline) {
|
||||||
|
console.log('[네트워크] 재연결 성공');
|
||||||
|
setNetworkStatus('online');
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
|
||||||
|
// 대기 중인 동기화 작업 처리
|
||||||
|
processPendingSyncQueue().catch(err => {
|
||||||
|
console.error('[네트워크] 재연결 후 동기화 큐 처리 중 오류:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('[네트워크] 재연결 실패, 재시도 예정');
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
|
||||||
|
// 다음 재연결 시도 예약
|
||||||
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 재연결 시도 중 오류:', error);
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
|
||||||
|
// 다음 재연결 시도 예약
|
||||||
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 상태 모니터링 시작
|
||||||
|
*/
|
||||||
|
export const startNetworkMonitoring = (): void => {
|
||||||
|
if (isMonitoring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMonitoring = true;
|
||||||
|
|
||||||
|
// 초기 네트워크 상태를 온라인으로 설정 (실제 확인 전)
|
||||||
|
setNetworkStatus('online');
|
||||||
|
|
||||||
|
// 초기 네트워크 상태 확인
|
||||||
|
checkNetworkStatus().then(isOnline => {
|
||||||
|
if (!isOnline) {
|
||||||
|
// 오프라인 상태면 재연결 시도 시작
|
||||||
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 브라우저 온라인/오프라인 이벤트 리스너
|
||||||
|
const handleOnline = () => {
|
||||||
|
console.log('[네트워크] 브라우저 온라인 이벤트 감지');
|
||||||
|
setNetworkStatus('online');
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
|
||||||
|
// 대기 중인 동기화 작업 처리
|
||||||
|
processPendingSyncQueue().catch(err => {
|
||||||
|
console.error('[네트워크] 온라인 전환 후 동기화 큐 처리 중 오류:', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOffline = () => {
|
||||||
|
console.log('[네트워크] 브라우저 오프라인 이벤트 감지');
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
|
||||||
|
// 재연결 시도 시작
|
||||||
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 리스너 등록 및 참조 저장
|
||||||
|
window.addEventListener('online', handleOnline);
|
||||||
|
window.addEventListener('offline', handleOffline);
|
||||||
|
|
||||||
|
// 전역 변수에 이벤트 핸들러 참조 저장
|
||||||
|
(window as ExtendedWindow).__networkHandlers = {
|
||||||
|
handleOnline,
|
||||||
|
handleOffline
|
||||||
|
};
|
||||||
|
|
||||||
|
// 정기적인 네트워크 상태 확인 (30초마다)
|
||||||
|
networkCheckInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const isOnline = await checkNetworkStatus();
|
||||||
|
const currentStatus = getNetworkStatus();
|
||||||
|
|
||||||
|
if (isOnline && currentStatus !== 'online') {
|
||||||
|
// 오프라인에서 온라인으로 변경
|
||||||
|
setNetworkStatus('online');
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
|
||||||
|
// 대기 중인 동기화 작업 처리
|
||||||
|
processPendingSyncQueue().catch(err => {
|
||||||
|
console.error('[네트워크] 정기 확인 후 동기화 큐 처리 중 오류:', err);
|
||||||
|
});
|
||||||
|
} else if (!isOnline && currentStatus === 'online') {
|
||||||
|
// 온라인에서 오프라인으로 변경
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
|
||||||
|
// 재연결 시도 시작
|
||||||
|
if (!reconnectTimer) {
|
||||||
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 정기 네트워크 상태 확인 중 오류:', error);
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
console.log('[네트워크] 네트워크 모니터링 시작');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 상태 모니터링 중지
|
||||||
|
*/
|
||||||
|
export const stopNetworkMonitoring = (): void => {
|
||||||
|
if (!isMonitoring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMonitoring = false;
|
||||||
|
|
||||||
|
// 브라우저 이벤트 리스너 제거
|
||||||
|
const handlers = (window as ExtendedWindow).__networkHandlers;
|
||||||
|
if (handlers) {
|
||||||
|
window.removeEventListener('online', handlers.handleOnline);
|
||||||
|
window.removeEventListener('offline', handlers.handleOffline);
|
||||||
|
delete (window as ExtendedWindow).__networkHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인터벌 및 타이머 정리
|
||||||
|
if (networkCheckInterval) {
|
||||||
|
clearInterval(networkCheckInterval);
|
||||||
|
networkCheckInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
|
||||||
|
console.log('[네트워크] 네트워크 모니터링 중지');
|
||||||
|
};
|
||||||
138
src/utils/network/queue.ts
Normal file
138
src/utils/network/queue.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 동기화 작업 큐 관리 유틸리티
|
||||||
|
*/
|
||||||
|
import { SyncQueueItem } from './types';
|
||||||
|
import { getNetworkStatus } from './status';
|
||||||
|
|
||||||
|
// 동기화 작업 큐 관리
|
||||||
|
const SYNC_QUEUE_KEY = 'sync_queue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 작업을 큐에 추가
|
||||||
|
*/
|
||||||
|
export const addToSyncQueue = (operation: {
|
||||||
|
type: 'upload' | 'download' | 'delete';
|
||||||
|
entityType: 'transaction' | 'budget' | 'categoryBudget';
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
timestamp?: number;
|
||||||
|
retries?: number;
|
||||||
|
}): void => {
|
||||||
|
// 타임스탬프 및 재시도 횟수 추가
|
||||||
|
const enhancedOperation = {
|
||||||
|
...operation,
|
||||||
|
timestamp: operation.timestamp || Date.now(),
|
||||||
|
retries: operation.retries || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 로컬 스토리지에서 기존 큐 가져오기
|
||||||
|
const queueString = localStorage.getItem(SYNC_QUEUE_KEY) || '[]';
|
||||||
|
let queue: typeof enhancedOperation[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
queue = JSON.parse(queueString);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 동기화 큐 파싱 오류:', error);
|
||||||
|
queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 큐에 작업 추가
|
||||||
|
queue.push(enhancedOperation);
|
||||||
|
|
||||||
|
// 큐 저장
|
||||||
|
localStorage.setItem(SYNC_QUEUE_KEY, JSON.stringify(queue));
|
||||||
|
|
||||||
|
console.log(`[네트워크] 동기화 큐에 작업 추가: ${operation.type} ${operation.entityType}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 큐 가져오기
|
||||||
|
*/
|
||||||
|
export const getSyncQueue = (): SyncQueueItem[] => {
|
||||||
|
try {
|
||||||
|
const queueString = localStorage.getItem(SYNC_QUEUE_KEY) || '[]';
|
||||||
|
return JSON.parse(queueString);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 동기화 큐 조회 실패:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 큐에서 항목 제거
|
||||||
|
*/
|
||||||
|
export const removeFromSyncQueue = (itemId: string): void => {
|
||||||
|
try {
|
||||||
|
const queue = getSyncQueue();
|
||||||
|
const updatedQueue = queue.filter(item => item.id !== itemId);
|
||||||
|
localStorage.setItem(SYNC_QUEUE_KEY, JSON.stringify(updatedQueue));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 동기화 큐 항목 제거 실패:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 큐 항목 업데이트
|
||||||
|
*/
|
||||||
|
export const updateSyncQueueItem = (itemId: string, updates: Partial<SyncQueueItem>): void => {
|
||||||
|
try {
|
||||||
|
const queue = getSyncQueue();
|
||||||
|
const updatedQueue = queue.map(item =>
|
||||||
|
item.id === itemId ? { ...item, ...updates } : item
|
||||||
|
);
|
||||||
|
localStorage.setItem(SYNC_QUEUE_KEY, JSON.stringify(updatedQueue));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[네트워크] 동기화 큐 항목 업데이트 실패:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보류 중인 동기화 작업 처리
|
||||||
|
*/
|
||||||
|
export const processPendingSyncQueue = async (): Promise<void> => {
|
||||||
|
// 현재 네트워크 상태 확인
|
||||||
|
const networkStatus = getNetworkStatus();
|
||||||
|
if (networkStatus !== 'online') {
|
||||||
|
console.log('[네트워크] 오프라인 상태에서 동기화 큐 처리 불가');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 큐 가져오기
|
||||||
|
const queue = getSyncQueue();
|
||||||
|
if (queue.length === 0) {
|
||||||
|
console.log('[네트워크] 처리할 동기화 작업 없음');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[네트워크] 동기화 큐 처리 시작 (${queue.length}개 항목)`);
|
||||||
|
|
||||||
|
// 작업 처리
|
||||||
|
for (const item of queue) {
|
||||||
|
try {
|
||||||
|
console.log(`[네트워크] 동기화 작업 처리 중: ${item.type} ${item.entityType}`);
|
||||||
|
|
||||||
|
// TODO: 실제 동기화 작업 처리 로직 구현
|
||||||
|
// 예: 서버에 데이터 업로드, 다운로드 등
|
||||||
|
|
||||||
|
// 성공 시 큐에서 제거
|
||||||
|
removeFromSyncQueue(item.id);
|
||||||
|
console.log(`[네트워크] 동기화 작업 성공: ${item.type} ${item.entityType}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[네트워크] 동기화 작업 실패: ${item.type} ${item.entityType}`, error);
|
||||||
|
|
||||||
|
// 재시도 횟수 증가
|
||||||
|
const retryCount = (item.retryCount || 0) + 1;
|
||||||
|
|
||||||
|
if (retryCount <= 3) {
|
||||||
|
// 재시도 횟수가 3회 이하면 업데이트
|
||||||
|
updateSyncQueueItem(item.id, { retryCount });
|
||||||
|
console.log(`[네트워크] 동기화 작업 재시도 예정 (${retryCount}/3): ${item.type} ${item.entityType}`);
|
||||||
|
} else {
|
||||||
|
// 재시도 횟수 초과 시 제거
|
||||||
|
removeFromSyncQueue(item.id);
|
||||||
|
console.log(`[네트워크] 동기화 작업 최대 재시도 횟수 초과로 제거: ${item.type} ${item.entityType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[네트워크] 동기화 큐 처리 완료');
|
||||||
|
};
|
||||||
71
src/utils/network/retry.ts
Normal file
71
src/utils/network/retry.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 요청 재시도 유틸리티
|
||||||
|
*/
|
||||||
|
import { RetryOptions } from './types';
|
||||||
|
|
||||||
|
// 네트워크 재시도 설정
|
||||||
|
const MAX_RETRY_COUNT = 3;
|
||||||
|
const INITIAL_RETRY_DELAY = 1000; // 1초
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 요청 래퍼 함수 (재시도 로직 포함)
|
||||||
|
*/
|
||||||
|
export const withRetry = async <T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options: RetryOptions<T> = {}
|
||||||
|
): Promise<T> => {
|
||||||
|
const {
|
||||||
|
maxRetries = MAX_RETRY_COUNT,
|
||||||
|
retryDelay = INITIAL_RETRY_DELAY,
|
||||||
|
onRetry = () => {},
|
||||||
|
shouldRetry = () => true
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let lastError: Error | unknown;
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
// 함수 실행
|
||||||
|
return await fn();
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
|
||||||
|
// 마지막 시도였으면 에러 발생
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 여부 확인
|
||||||
|
if (!shouldRetry(error)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 콜백 호출
|
||||||
|
onRetry(attempt + 1, error);
|
||||||
|
|
||||||
|
// 지수 백오프 (exponential backoff) 적용
|
||||||
|
const delay = retryDelay * Math.pow(2, attempt);
|
||||||
|
console.log(`[네트워크] ${attempt + 1}번째 재시도 ${delay}ms 후 실행`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 요청 타임아웃 래퍼 함수
|
||||||
|
*/
|
||||||
|
export const withTimeout = async <T>(
|
||||||
|
promise: Promise<T>,
|
||||||
|
timeoutMs: number = 10000
|
||||||
|
): Promise<T> => {
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise<T>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error(`요청 시간 초과 (${timeoutMs}ms)`));
|
||||||
|
}, timeoutMs);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
};
|
||||||
43
src/utils/network/status.ts
Normal file
43
src/utils/network/status.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 상태 관리 유틸리티
|
||||||
|
*/
|
||||||
|
import { NetworkStatus } from './types';
|
||||||
|
|
||||||
|
// 메모리에 네트워크 상태 저장
|
||||||
|
let networkStatus: NetworkStatus | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 상태 저장 (메모리 기반)
|
||||||
|
*/
|
||||||
|
export const setNetworkStatus = (status: NetworkStatus): void => {
|
||||||
|
// 메모리에 상태 저장
|
||||||
|
networkStatus = status;
|
||||||
|
|
||||||
|
// 상태 변경 이벤트 발생
|
||||||
|
window.dispatchEvent(new CustomEvent('networkStatusChange', { detail: status }));
|
||||||
|
console.log(`[네트워크] 상태 변경: ${status}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 상태 가져오기 (메모리 기반)
|
||||||
|
*/
|
||||||
|
export const getNetworkStatus = (): NetworkStatus => {
|
||||||
|
return networkStatus || 'online';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크 상태 변경 이벤트 리스너 등록
|
||||||
|
*/
|
||||||
|
export const onNetworkStatusChange = (callback: (status: NetworkStatus) => void): () => void => {
|
||||||
|
const handler = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent<NetworkStatus>;
|
||||||
|
callback(customEvent.detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('networkStatusChange', handler);
|
||||||
|
|
||||||
|
// 구독 취소 함수 반환
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('networkStatusChange', handler);
|
||||||
|
};
|
||||||
|
};
|
||||||
35
src/utils/network/types.ts
Normal file
35
src/utils/network/types.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 네트워크 관련 타입 정의
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 네트워크 상태 타입
|
||||||
|
export type NetworkStatus = 'online' | 'offline' | 'reconnecting';
|
||||||
|
|
||||||
|
// 동기화 작업 큐 아이템 타입
|
||||||
|
export interface SyncQueueItem {
|
||||||
|
id: string;
|
||||||
|
type: 'upload' | 'download' | 'delete';
|
||||||
|
entityType: 'transaction' | 'budget' | 'categoryBudget';
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
timestamp: number;
|
||||||
|
retryCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 네트워크 이벤트 핸들러 인터페이스
|
||||||
|
export interface NetworkEventHandlers {
|
||||||
|
handleOnline: () => void;
|
||||||
|
handleOffline: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 확장된 Window 인터페이스
|
||||||
|
export interface ExtendedWindow extends Window {
|
||||||
|
__networkHandlers?: NetworkEventHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 옵션 인터페이스
|
||||||
|
export interface RetryOptions<T> {
|
||||||
|
maxRetries?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
onRetry?: (attempt: number, error: Error | unknown) => void;
|
||||||
|
shouldRetry?: (error: Error | unknown) => boolean;
|
||||||
|
}
|
||||||
@@ -62,19 +62,37 @@ export const checkNetworkStatus = async (): Promise<boolean> => {
|
|||||||
'https://www.apple.com'
|
'https://www.apple.com'
|
||||||
];
|
];
|
||||||
|
|
||||||
// 첫 번째 성공 응답 시 온라인으로 판단
|
try {
|
||||||
const isOnline = await Promise.any(
|
// 각 엔드포인트에 대해 fetch 요청 시도
|
||||||
endpoints.map(url =>
|
const fetchPromises = endpoints.map(url =>
|
||||||
fetch(url, { method: 'HEAD', signal: controller.signal })
|
fetch(url, { method: 'HEAD', signal: controller.signal })
|
||||||
.then(res => res.ok)
|
.then(res => {
|
||||||
)
|
if (res.ok) return true;
|
||||||
);
|
throw new Error(`Failed to fetch ${url}`);
|
||||||
|
})
|
||||||
clearTimeout(timeoutId);
|
.catch(err => {
|
||||||
setNetworkStatus(isOnline ? 'online' : 'offline');
|
console.log(`[네트워크] ${url} 연결 실패:`, err.message);
|
||||||
return isOnline;
|
return false; // 실패 시 false 반환
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 모든 요청을 병렬로 실행하고 결과 확인
|
||||||
|
const results = await Promise.all(fetchPromises);
|
||||||
|
|
||||||
|
// 하나라도 성공했으면 온라인으로 판단
|
||||||
|
const isOnline = results.some(result => result === true);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setNetworkStatus(isOnline ? 'online' : 'offline');
|
||||||
|
return isOnline;
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
console.error('[네트워크] 네트워크 확인 실패:', error);
|
||||||
|
setNetworkStatus('offline');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[네트워크] 연결 확인 실패:', error);
|
console.error('[네트워크] 연결 확인 중 예상치 못한 오류:', error);
|
||||||
setNetworkStatus('offline');
|
setNetworkStatus('offline');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -100,7 +118,7 @@ export const getNetworkStatus = (): NetworkStatus => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 네트워크 상태 모니터링 시작
|
* 네트워크 상태 모니터링 시작 함수 개선
|
||||||
*/
|
*/
|
||||||
export const startNetworkMonitoring = (): void => {
|
export const startNetworkMonitoring = (): void => {
|
||||||
if (isMonitoring) {
|
if (isMonitoring) {
|
||||||
@@ -109,10 +127,11 @@ export const startNetworkMonitoring = (): void => {
|
|||||||
|
|
||||||
isMonitoring = true;
|
isMonitoring = true;
|
||||||
|
|
||||||
|
// 초기 네트워크 상태를 온라인으로 설정 (실제 확인 전)
|
||||||
|
setNetworkStatus('online');
|
||||||
|
|
||||||
// 초기 네트워크 상태 확인
|
// 초기 네트워크 상태 확인
|
||||||
checkNetworkStatus().then(isOnline => {
|
checkNetworkStatus().then(isOnline => {
|
||||||
setNetworkStatus(isOnline ? 'online' : 'offline');
|
|
||||||
|
|
||||||
if (!isOnline) {
|
if (!isOnline) {
|
||||||
// 오프라인 상태면 재연결 시도 시작
|
// 오프라인 상태면 재연결 시도 시작
|
||||||
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
reconnectTimer = setTimeout(attemptReconnect, RECONNECT_INTERVAL);
|
||||||
|
|||||||
Reference in New Issue
Block a user