iframe으로 전환된 개발자 도구에 접근할 방법이 필요했다. URL 직접 접근은 차단되었고, 사용자에게 노출되지 않는 "조용한" 접근 방식이 필요했다.
이 문서를 읽고 나면:
event.key와 event.code의 차이를 이해할 수 있다특정 키 시퀀스 입력 시 페이지 이동 (예: devmode 입력)
// ❌ 문제가 있던 초기 구현
const SECRET_SEQUENCE = ['d', 'e', 'v', 'm', 'o', 'd', 'e'];
const SECRET_SEQUENCE_KOREAN = ['ㄹ', 'ㄷ', 'ㅍ', 'ㅡ', 'ㅗ', 'ㄹ', 'ㄷ'];
const handleKeyPress = (event: KeyboardEvent) => {
const key = event.key.toLowerCase();
// 영문자 또는 한글 자음만 허용
if (!/^[a-z]$/.test(key) && !/^[ㄱ-ㅎ]$/.test(key)) {
return;
}
// 영문/한글 시퀀스 각각 확인
const isEnglishMatch = newSequence.every(
(k, index) => k === SECRET_SEQUENCE[index]
);
const isKoreanMatch = newSequence.every(
(k, index) => k === SECRET_SEQUENCE_KOREAN[index]
);
};
event.key로 입력된 문자 감지사용자 입력: ㄹㄷㅍㅡㅗㄹㄷ (devmode의 한글 대응)
실제 감지: ㄹㄷㅍㄹㄷ (ㅡ, ㅗ 모음이 사라짐)
🔍 Key pressed: ㄹ ✅
🔍 Key pressed: ㄷ ✅
🔍 Key pressed: ㅍ ✅
🚫 Key filtered out: ㅡ ❌ (모음이라 필터됨)
🚫 Key filtered out: ㅗ ❌ (모음이라 필터됨)
🔍 Key pressed: ㄹ ✅
🔍 Key pressed: ㄷ ✅
❌ Wrong sequence: [ㄹ,ㄷ,ㅍ,ㄹ,ㄷ] vs [ㄹ,ㄷ,ㅍ,ㅡ,ㅗ,ㄹ,ㄷ]
if (!/^[a-z]$/.test(key) && !/^[ㄱ-ㅎ]$/.test(key)) {
return;
}
자음만 허용하는 필터가 한글 모음을 차단했다.
| 키 위치 | 영문 모드 | 한글 모드 | 문제점 |
|---|---|---|---|
| M키 | "m" | "ㅡ" (모음) | 자음 필터에 걸림 |
| O키 | "o" | "ㅗ" (모음) | 자음 필터에 걸림 |
필요한 필터가 계속 증가:
이는 유지보수가 어렵고 확장성이 없는 구조다.
event.key: "무엇이 입력되었나?" (문자 중심)
event.code: "어디가 눌렸나?" (물리적 키 위치 중심)
| 상황 | event.key | event.code | 결과 |
|---|---|---|---|
| 영문 D키 | "d" | "KeyD" | ✅ 일관성 |
| 한글 D키 | "ㄹ" | "KeyD" | ✅ 일관성 |
| 영문 M키 | "m" | "KeyM" | ✅ 일관성 |
| 한글 M키 | "ㅡ" (모음) | "KeyM" | ✅ 일관성 |
| CapsLock | 영향받음 | 영향받지 않음 | ✅ 안정성 |
// ✅ 개선된 구현
const SECRET_SEQUENCE_CODES <= [
'KeyD', 'KeyE', 'KeyV', 'KeyM', 'KeyO', 'KeyD', 'KeyE'
];
const handleKeyPress = (event: KeyboardEvent) => {
const code = event.code;
// 해당 키 위치가 시퀀스에 포함되는지만 확인
if (!SECRET_SEQUENCE_CODES.includes(code)) {
return;
}
// 단일 시퀀스로 모든 입력 모드 처리
const isMatch = newSequence.every(
(k, index) => k === SECRET_SEQUENCE_CODES[index]
);
};
개선 효과:
'use client';
import { useEffect, useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
// 물리적 키 코드 기반 시퀀스
const SECRET_SEQUENCE_CODES = [
'KeyD',
'KeyE',
'KeyV',
'KeyM',
'KeyO',
'KeyD',
'KeyE',
];
const SEQUENCE_TIMEOUT = 5000; // 5초 내에 입력
export const useDevToolsAccess = () => {
const router = useRouter();
const [keySequence, setKeySequence] = useState<string[]>([]);
const [lastKeyTime, setLastKeyTime] = useState<number>(0);
const handleKeyPress = useCallback(
(event: KeyboardEvent) => {
const currentTime = Date.now();
const code = event.code;
// CapsLock 키 무시
if (event.key === 'CapsLock') {
return;
}
// 물리적 키 코드가 시퀀스에 포함되는지 확인
if (!SECRET_SEQUENCE_CODES.includes(code)) {
return;
}
setKeySequence((prevSequence) => {
let newSequence: string[];
// 시간 초과 시 시퀀스 리셋
if (currentTime - lastKeyTime > SEQUENCE_TIMEOUT) {
newSequence = [code];
} else {
newSequence = [...prevSequence, code];
}
// 시퀀스 완성 확인
if (newSequence.length === SECRET_SEQUENCE_CODES.length) {
const isMatch = newSequence.every(
(k, index) => k === SECRET_SEQUENCE_CODES[index]
);
if (isMatch) {
setTimeout(() => router.push('/dev-tools'), 0);
return [];
} else {
return [];
}
}
// 시퀀스가 너무 길면 마지막 N개만 유지
if (newSequence.length > SECRET_SEQUENCE_CODES.length) {
return newSequence.slice(-SECRET_SEQUENCE_CODES.length + 1);
}
return newSequence;
});
setLastKeyTime(currentTime);
},
[lastKeyTime, router]
);
useEffect(() => {
document.addEventListener('keydown', handleKeyPress);
return () => {
document.removeEventListener('keydown', handleKeyPress);
};
}, [handleKeyPress]);
return {
currentSequence: process.env.NODE_ENV === 'development' ? keySequence : [],
isActive:
process.env.NODE_ENV === 'development' ? keySequence.length > 0 : false,
};
};
if (currentTime - lastKeyTime > SEQUENCE_TIMEOUT) {
newSequence = [code];
}
5초 이내에 시퀀스를 완성해야 한다.
if (newSequence.length > SECRET_SEQUENCE_CODES.length) {
return newSequence.slice(-SECRET_SEQUENCE_CODES.length + 1);
}
불필요하게 긴 시퀀스를 방지한다.
return {
currentSequence: process.env.NODE_ENV === 'development' ? keySequence : [],
isActive: process.env.NODE_ENV === 'development' ? keySequence.length > 0 : false,
};
개발 환경에서만 현재 진행 상황을 노출한다.
event.key: 문자 중심, 입력 모드에 영향받음event.code: 위치 중심, 입력 모드와 무관console.log('Key:', event.key, 'Code:', event.code);
두 값을 함께 로깅하여 차이점을 명확히 파악할 수 있다.