239 lines
5.8 KiB
TypeScript
239 lines
5.8 KiB
TypeScript
/**
|
|
* 세션 관리 유틸리티
|
|
* 세션 만료 감지, 자동 로그아웃, 세션 갱신 등을 담당
|
|
*/
|
|
|
|
interface SessionConfig {
|
|
checkInterval: number; // 세션 체크 간격 (ms)
|
|
warningTime: number; // 만료 경고 시간 (ms)
|
|
maxInactiveTime: number; // 최대 비활성 시간 (ms)
|
|
}
|
|
|
|
interface SessionWarningCallbacks {
|
|
onWarning?: (remainingTime: number) => void;
|
|
onExpiry?: () => void;
|
|
onActivity?: () => void;
|
|
}
|
|
|
|
export class SessionManager {
|
|
private config: SessionConfig;
|
|
private callbacks: SessionWarningCallbacks;
|
|
private checkTimer: NodeJS.Timeout | null = null;
|
|
private warningTimer: NodeJS.Timeout | null = null;
|
|
private lastActivity: number = Date.now();
|
|
private isWarningShown: boolean = false;
|
|
|
|
constructor(config: Partial<SessionConfig> = {}, callbacks: SessionWarningCallbacks = {}) {
|
|
this.config = {
|
|
checkInterval: 60000, // 1분마다 체크
|
|
warningTime: 300000, // 5분 전 경고
|
|
maxInactiveTime: 1800000, // 30분 비활성 시 만료
|
|
...config,
|
|
};
|
|
|
|
this.callbacks = callbacks;
|
|
this.setupActivityListeners();
|
|
}
|
|
|
|
/**
|
|
* 세션 모니터링 시작
|
|
*/
|
|
start() {
|
|
this.stop(); // 기존 타이머 정리
|
|
|
|
this.checkTimer = setInterval(() => {
|
|
this.checkSession();
|
|
}, this.config.checkInterval);
|
|
|
|
console.log("세션 모니터링 시작됨");
|
|
}
|
|
|
|
/**
|
|
* 세션 모니터링 중지
|
|
*/
|
|
stop() {
|
|
if (this.checkTimer) {
|
|
clearInterval(this.checkTimer);
|
|
this.checkTimer = null;
|
|
}
|
|
|
|
if (this.warningTimer) {
|
|
clearTimeout(this.warningTimer);
|
|
this.warningTimer = null;
|
|
}
|
|
|
|
this.removeActivityListeners();
|
|
console.log("세션 모니터링 중지됨");
|
|
}
|
|
|
|
/**
|
|
* 사용자 활동 기록
|
|
*/
|
|
recordActivity() {
|
|
this.lastActivity = Date.now();
|
|
this.isWarningShown = false;
|
|
|
|
// 경고 타이머가 있다면 취소
|
|
if (this.warningTimer) {
|
|
clearTimeout(this.warningTimer);
|
|
this.warningTimer = null;
|
|
}
|
|
|
|
this.callbacks.onActivity?.();
|
|
}
|
|
|
|
/**
|
|
* 세션 상태 확인
|
|
*/
|
|
private checkSession() {
|
|
const now = Date.now();
|
|
const timeSinceLastActivity = now - this.lastActivity;
|
|
const timeUntilExpiry = this.config.maxInactiveTime - timeSinceLastActivity;
|
|
|
|
// 세션 만료 체크
|
|
if (timeSinceLastActivity >= this.config.maxInactiveTime) {
|
|
this.handleSessionExpiry();
|
|
return;
|
|
}
|
|
|
|
// 경고 시간 체크
|
|
if (timeUntilExpiry <= this.config.warningTime && !this.isWarningShown) {
|
|
this.showSessionWarning(timeUntilExpiry);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 세션 만료 경고 표시
|
|
*/
|
|
private showSessionWarning(remainingTime: number) {
|
|
this.isWarningShown = true;
|
|
this.callbacks.onWarning?.(remainingTime);
|
|
|
|
// 남은 시간 후 자동 만료
|
|
this.warningTimer = setTimeout(() => {
|
|
this.handleSessionExpiry();
|
|
}, remainingTime);
|
|
}
|
|
|
|
/**
|
|
* 세션 만료 처리
|
|
*/
|
|
private handleSessionExpiry() {
|
|
console.log("세션이 만료되었습니다");
|
|
this.stop();
|
|
this.callbacks.onExpiry?.();
|
|
}
|
|
|
|
/**
|
|
* 활동 리스너 설정
|
|
*/
|
|
private setupActivityListeners() {
|
|
// 사용자 활동 이벤트들
|
|
const activityEvents = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
|
|
|
|
// 각 이벤트에 대해 리스너 등록
|
|
activityEvents.forEach((event) => {
|
|
document.addEventListener(event, this.handleActivity, true);
|
|
});
|
|
|
|
// 페이지 가시성 변경 이벤트
|
|
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
}
|
|
|
|
/**
|
|
* 활동 리스너 제거
|
|
*/
|
|
private removeActivityListeners() {
|
|
const activityEvents = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
|
|
|
|
activityEvents.forEach((event) => {
|
|
document.removeEventListener(event, this.handleActivity, true);
|
|
});
|
|
|
|
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
}
|
|
|
|
/**
|
|
* 활동 이벤트 핸들러
|
|
*/
|
|
private handleActivity = () => {
|
|
this.recordActivity();
|
|
};
|
|
|
|
/**
|
|
* 페이지 가시성 변경 핸들러
|
|
*/
|
|
private handleVisibilityChange = () => {
|
|
if (!document.hidden) {
|
|
this.recordActivity();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 현재 세션 상태 정보 반환
|
|
*/
|
|
getSessionInfo() {
|
|
const now = Date.now();
|
|
const timeSinceLastActivity = now - this.lastActivity;
|
|
const timeUntilExpiry = this.config.maxInactiveTime - timeSinceLastActivity;
|
|
|
|
return {
|
|
lastActivity: this.lastActivity,
|
|
timeSinceLastActivity,
|
|
timeUntilExpiry: Math.max(0, timeUntilExpiry),
|
|
isActive: timeSinceLastActivity < this.config.maxInactiveTime,
|
|
isWarningShown: this.isWarningShown,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 시간을 분:초 형식으로 포맷
|
|
*/
|
|
export function formatTime(milliseconds: number): string {
|
|
const seconds = Math.floor(milliseconds / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
/**
|
|
* 전역 세션 매니저 인스턴스
|
|
*/
|
|
let globalSessionManager: SessionManager | null = null;
|
|
|
|
/**
|
|
* 전역 세션 매니저 초기화
|
|
*/
|
|
export function initSessionManager(
|
|
config: Partial<SessionConfig> = {},
|
|
callbacks: SessionWarningCallbacks = {},
|
|
): SessionManager {
|
|
if (globalSessionManager) {
|
|
globalSessionManager.stop();
|
|
}
|
|
|
|
globalSessionManager = new SessionManager(config, callbacks);
|
|
return globalSessionManager;
|
|
}
|
|
|
|
/**
|
|
* 전역 세션 매니저 가져오기
|
|
*/
|
|
export function getSessionManager(): SessionManager | null {
|
|
return globalSessionManager;
|
|
}
|
|
|
|
/**
|
|
* 세션 매니저 정리
|
|
*/
|
|
export function cleanupSessionManager() {
|
|
if (globalSessionManager) {
|
|
globalSessionManager.stop();
|
|
globalSessionManager = null;
|
|
}
|
|
}
|
|
|
|
export default SessionManager;
|