본문으로 바로가기
윤창원uiwwsw · 작은 우주

만드는 이야기

키 입력 시퀀스로 만드는 이스터에그 — 보안 기능과 구분하기

2026-09-10: 설명과 코드 예시를 보완했습니다. 아래 예시는 글의 설계 의도를 전달하기 위한 것이며, 원 프로젝트에 반영된 변경 내역과는 구분합니다.

오래된 게임의 비밀 코드처럼 정해진 키 순서를 입력하면 작은 메시지가 나타나는 기능을 만들어봤다. 키 입력을 모아 시퀀스와 비교하고, 일치하면 콜백을 실행하는 방식이다.

프로젝트 저장소

이 기능이 맡을 역할

이스터에그는 발견하는 재미를 위한 장치다. 시퀀스를 Base64로 바꾸거나 개발자 도구의 크기를 추측해도 관리자 권한을 보호하는 보안 경계가 되지는 않는다. Base64는 디코딩할 수 있는 인코딩이다. MDN btoa

민감한 작업의 인증과 권한은 서버에서 확인해야 한다. 이 예시는 메시지, 애니메이션, 개발 환경의 상태 표시처럼 공개되어도 되는 동작을 대상으로 한다.

핵심 구현

이전 예시의 난독화와 디버거 탐지를 덜어내고 입력 비교와 구독 해제에 집중했다.

export function createEasterEgg(
  target: HTMLElement,
  callback: () => void,
  keySequence: readonly string[],
): () => void {
  const sequence = [...keySequence];
  if (sequence.length === 0) return () => {};
  const recent: string[] = [];

  const handleKeyDown = (event: KeyboardEvent) => {
    if (event.isComposing || event.repeat) return;
    if (event.target instanceof Element &&
        event.target.closest('input, textarea, [contenteditable]')) return;

    recent.push(event.key);
    if (recent.length > sequence.length) recent.shift();

    if (recent.length === sequence.length &&
        sequence.every((key, index) => key === recent[index])) {
      recent.length = 0;
      callback();
    }
  };

  target.addEventListener('keydown', handleKeyDown);
  return () => target.removeEventListener('keydown', handleKeyDown);
}

최근 입력을 시퀀스 길이만큼 보관하므로, 틀린 입력 뒤에 정답 시퀀스를 이어 입력해도 다시 감지할 수 있다. 입력 필드와 조합 중인 키는 건드리지 않도록 제외했다.

const dispose = createEasterEgg(
  document.body,
  () => alert('이스터에그를 찾았습니다!'),
  ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown',
   'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'],
);

// 화면이나 기능이 정리되는 시점에 호출한다.
// dispose();

React에서 사용한다면 effect의 cleanup으로 dispose를 반환하면 된다. 키보드 입력만을 다루는 예시이며 모바일 접근이 필요하면 별도의 진입 방법을 제공해야 한다.

작은 재미를 만드는 코드도 역할이 분명하면 유지하기 쉽다. 숨겨진 기능이라는 이유로 권한 확인까지 대신하게 만들지 않는 것이 이 예시의 기준이다.

Assisted by AI

윤창원이 벨로그에 남긴 글을 이 작은 우주에도 모았습니다. 사진은 누르면 원본 크기로 볼 수 있습니다. 원문의 전체 서식 보기 ↗

모든 글 둘러보기 →