@coffeeandfun/konami-code-detector
v1.0.1
Published
A modern, async-enabled Konami Code detector
Maintainers
Readme
🎮 @coffeeandfun/konami-code-detector
A tiny, modern library that watches for the Konami Code (↑ ↑ ↓ ↓ ← → ← → B A) — or any key sequence you pick — and runs a callback when the user finishes it.
- 🪶 Zero runtime dependencies
- 🚀 Ships as native ESM with TypeScript declarations
- 🧩 Works with React, Vue, Svelte, Angular, plain JavaScript, Chrome extensions — anywhere with a DOM
- ⚙️ Configurable: custom sequences, cooldowns, one-shot mode, max-attempts, progress events, async callbacks
- 🧪 Fully tested with Jest + jsdom
📦 Install
npm install @coffeeandfun/konami-code-detectorRequires Node.js 16+ to install. In the browser it works anywhere AbortController and modern classes are supported (Chrome 66+, Firefox 57+, Safari 11.1+, Edge 79+).
⚡ Quick start
import KonamiCode from '@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(() => {
document.body.classList.add('party-mode');
});
await konami.enable();
// Press ↑ ↑ ↓ ↓ ← → ← → B A — the callback fires 🎉That's it. Everything else in this README is optional.
🧠 Mental model
You create an instance, give it a callback, call enable(), and the detector listens for keydown events. Every correct key in the sequence fires a progress event. A wrong key resets progress and fires a failed event. When the user finishes the sequence:
- Your callback runs (sync or async — both are awaited).
- An
activatedevent fires. - A bubbling
konamicodeCustomEventis dispatched on your target, so code outside the instance can react too.
If you stop caring, call destroy() and it cleans itself up.
🖼️ What an instance looks like
const konami = new KonamiCode(callback, options);
// →
{
callback, // the function you passed
config: {
sequence, // resolved key list
timeout, // ms of inactivity before progress resets
target, // element the keydown listener is on
preventDefault, // whether to preventDefault on the final key
once, // fire at most once?
debug, // log state transitions?
maxAttempts, // cap on activations
cooldown, // ms between activations
},
attempts: 0, // successful activations
stats: {
activations: 0,
attempts: 0, // failed attempts (wrong keys)
lastActivated: null, // ms timestamp of last success
},
// plus the methods documented below
}🧪 Examples by framework
Each example is self-contained and does the same thing: fires a callback when the Konami code is entered. Pick the one that matches your stack.
🟨 Vanilla JavaScript (with a bundler)
import KonamiCode from '@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(() => {
alert('🎉 You found the secret!');
});
konami.enable();🌐 Plain HTML, no bundler (ESM CDN)
<!doctype html>
<html>
<body>
<h1>Try the Konami code 👀</h1>
<script type="module">
import KonamiCode from 'https://esm.sh/@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(() => {
document.body.style.background = 'linear-gradient(45deg, #ff006e, #8338ec)';
});
konami.enable();
</script>
</body>
</html>⚛️ React (hook version)
import { useEffect, useRef } from 'react';
import KonamiCode from '@coffeeandfun/konami-code-detector';
export function App() {
const konamiRef = useRef(null);
useEffect(() => {
konamiRef.current = new KonamiCode(() => {
console.log('✨ activated in React!');
});
konamiRef.current.enable();
return () => {
konamiRef.current?.destroy();
};
}, []);
return <div>Try the Konami code 🎮</div>;
}⚛️ React (custom hook — reusable)
import { useEffect, useRef } from 'react';
import KonamiCode from '@coffeeandfun/konami-code-detector';
export function useKonamiCode(callback, options) {
const callbackRef = useRef(callback);
callbackRef.current = callback;
useEffect(() => {
const konami = new KonamiCode(() => callbackRef.current?.(), options);
konami.enable();
return () => { konami.destroy(); };
}, []);
}
// Usage:
function PartyButton() {
useKonamiCode(() => {
document.body.classList.add('party-mode');
});
return <button>Keep trying… 🕹️</button>;
}🟢 Vue 3 (Composition API)
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import KonamiCode from '@coffeeandfun/konami-code-detector';
const konami = ref(null);
onMounted(() => {
konami.value = new KonamiCode(() => console.log('🎉 activated in Vue!'));
konami.value.enable();
});
onUnmounted(() => {
konami.value?.destroy();
});
</script>
<template>
<p>Try the Konami code 🎮</p>
</template>🟢 Vue 3 (reusable composable)
// composables/useKonamiCode.js
import { onMounted, onUnmounted, ref } from 'vue';
import KonamiCode from '@coffeeandfun/konami-code-detector';
export function useKonamiCode(callback, options) {
const instance = ref(null);
onMounted(() => {
instance.value = new KonamiCode(callback, options);
instance.value.enable();
});
onUnmounted(() => {
instance.value?.destroy();
});
return instance;
}<script setup>
import { useKonamiCode } from './composables/useKonamiCode';
useKonamiCode(() => {
document.body.classList.add('party-mode');
});
</script>🔺 Svelte 4 / 5
<script>
import { onMount, onDestroy } from 'svelte';
import KonamiCode from '@coffeeandfun/konami-code-detector';
let konami;
onMount(() => {
konami = new KonamiCode(() => console.log('🎉 activated in Svelte!'));
konami.enable();
});
onDestroy(() => {
konami?.destroy();
});
</script>
<p>Try the Konami code 🎮</p>🅰️ Angular
import { Component, OnDestroy, OnInit } from '@angular/core';
import KonamiCode from '@coffeeandfun/konami-code-detector';
@Component({
selector: 'app-root',
template: '<p>Try the Konami code 🎮</p>',
})
export class AppComponent implements OnInit, OnDestroy {
private konami?: KonamiCode;
ngOnInit() {
this.konami = new KonamiCode(() => console.log('🎉 activated in Angular!'));
this.konami.enable();
}
ngOnDestroy() {
this.konami?.destroy();
}
}▲ Next.js (App Router — client component)
'use client';
import { useEffect, useRef } from 'react';
import KonamiCode from '@coffeeandfun/konami-code-detector';
export function KonamiListener() {
const ref = useRef<KonamiCode | null>(null);
useEffect(() => {
ref.current = new KonamiCode(() => {
console.log('🎉 activated on the client');
});
ref.current.enable();
return () => { ref.current?.destroy(); };
}, []);
return null;
}Drop <KonamiListener /> in your root layout. The 'use client' directive keeps it out of SSR where there's no document.
🧩 Chrome extension (content script)
import KonamiCode from '@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(async () => {
const response = await chrome.runtime.sendMessage({ type: 'KONAMI_ACTIVATED' });
console.log('unlocked 🔓', response);
});
konami.enable();// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'KONAMI_ACTIVATED') {
console.log('🎮 Konami activated on', sender.tab?.url);
sendResponse({ success: true });
}
});🧪 Node.js (headless testing)
The detector works without a DOM if you pass target: null and use the built-in triggerKey / triggerSequence helpers.
import KonamiCode, { SEQUENCES } from '@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(() => console.log('🎉 activated'), { target: null });
await konami.enable();
await konami.triggerSequence(); // → logs "🎉 activated"🍳 Recipes
Real patterns people use this for.
🎨 Party mode (add a CSS class)
const konami = new KonamiCode(() => {
document.body.classList.add('party-mode');
});
konami.enable();🐛 Hidden debug panel (custom sequence)
import KonamiCode, { SEQUENCES } from '@coffeeandfun/konami-code-detector';
const konami = new KonamiCode(
() => {
document.querySelector('#debug-panel').hidden = false;
localStorage.setItem('debugMode', 'true');
},
{ sequence: SEQUENCES.debug, once: true },
);
konami.enable();
// Type D-E-B-U-G to reveal the panel, once per page load.📊 Progress bar
const konami = new KonamiCode();
const bar = document.querySelector('#konami-progress');
konami
.on('progress', ({ percentage }) => { bar.style.width = `${percentage}%`; })
.on('failed', () => { bar.style.width = '0%'; })
.on('activated', () => { bar.style.width = '100%'; });
konami.enable();⏱️ Cooldown between activations
const konami = new KonamiCode(fireConfetti, { cooldown: 5000 });
await konami.enable();
// Subsequent activations inside 5s are silently dropped.🔂 Fire only once
const konami = new KonamiCode(unlockAchievement, { once: true });
await konami.enable();
// After the first activation, the instance disables itself automatically.🧭 Track analytics on activation
const konami = new KonamiCode();
konami.on('activated', ({ timestamp, activations }) => {
analytics.track('konami_code_activated', { timestamp, activations });
});
konami.enable();👂 Listen from outside the instance (DOM event)
const konami = new KonamiCode();
konami.enable();
document.addEventListener('konamicode', (event) => {
console.log('activated at', event.detail.timestamp);
console.log('stats:', event.detail.stats);
});🔐 Admin-only unlock (async check)
const konami = new KonamiCode(async () => {
const user = await fetchCurrentUser();
if (user.role === 'admin') {
showAdminPanel();
}
});
konami.enable();🎚️ Multiple codes, one page
const debugMode = new KonamiCode(enableDebug, { sequence: SEQUENCES.debug });
const godMode = new KonamiCode(enableGodMode, { sequence: ['g', 'o', 'd'], cooldown: 5000 });
const reset = new KonamiCode(resetApp, { sequence: ['r', 'e', 's', 'e', 't'], once: true });
await Promise.all([debugMode.enable(), godMode.enable(), reset.enable()]);📚 API reference
Constructor
new KonamiCode(callback?, options?)| Parameter | Type | Default | Description |
| ---------- | ---------- | ----------------- | ------------------------------------------------ |
| callback | Function | defaultCallback | Runs when the sequence completes. May be async. |
| options | Object | {} | See below. |
Options
| Option | Type | Default | Description |
| ---------------- | ------------- | ------------------- | ----------------------------------------------------------------------- |
| sequence | string[] | SEQUENCES.classic | Keys to detect, in order. Matched case-insensitively. |
| timeout | number | 1000 | Ms of inactivity before the in-progress sequence resets. |
| target | EventTarget | document | Element to listen on. Pass null for headless environments. |
| preventDefault | boolean | true | Call event.preventDefault() on the final matching keypress. |
| once | boolean | false | Fire at most once, then auto-disable. |
| debug | boolean | false | Log internal state transitions to the console. |
| maxAttempts | number | Infinity | Cap on successful activations. |
| cooldown | number | 0 | Minimum ms between successful activations. |
| autoEnable | boolean | false | Call enable() from the constructor. |
Methods
🔌 Lifecycle
| Method | Returns | Description |
| ------------- | ----------------------- | -------------------------------------------------- |
| enable() | Promise<KonamiCode> | Attach the keydown listener. |
| disable() | Promise<KonamiCode> | Detach the listener and reset progress. |
| reset() | void | Reset sequence progress without disabling. |
| destroy() | Promise<void> | Disable, clear event listeners, null the callback. |
⚙️ Configuration
| Method | Returns | Description |
| ------------------- | ------------ | -------------------------------------------- |
| setCallback(fn) | KonamiCode | Replace the activation callback. |
| setSequence(keys) | KonamiCode | Replace the key sequence and reset progress. |
🔍 State inspection
| Method | Returns | Description |
| --------------- | ------------------------------------------ | ----------------------------------------- |
| isEnabled() | boolean | Whether the listener is attached. |
| isActivated() | boolean | Whether the code has fired at least once. |
| getProgress() | { current, total, percentage } | Current position in the sequence. |
| getStats() | { activations, attempts, lastActivated } | Copy of stats. |
| resetStats() | KonamiCode | Zero out all stats. |
📣 Events
| Method | Returns | Description |
| --------------------- | ----------------- | ------------------------------------------- |
| on(event, handler) | KonamiCode | Register an event handler. |
| off(event, handler) | KonamiCode | Remove an event handler. |
| waitForActivation() | Promise<object> | Resolves on the next successful activation. |
🧪 Testing helpers
| Method | Returns | Description |
| ------------------- | --------------- | -------------------------------------------------- |
| triggerKey(key) | Promise<void> | Simulate a single keypress (no-op if not enabled). |
| triggerSequence() | Promise<void> | Simulate the full configured sequence. |
Events
Fired via on() / off(). Handlers may be async; the emitter awaits them all.
| Event | Payload | When it fires |
| ----------- | -------------------------------- | ----------------------------------------- |
| enabled | { timestamp } | After enable() attaches the listener. |
| disabled | { timestamp } | After disable() detaches it. |
| progress | { current, total, percentage } | On every correct key in the sequence. |
| activated | { timestamp, activations } | When the sequence completes. |
| failed | { key, expected } | When a wrong key is pressed mid-sequence. |
| error | { error, timestamp } | If the callback throws. |
A bubbling konamicode CustomEvent is also dispatched on the configured target when the code activates. Its detail is { timestamp, stats }.
Built-in sequences
import { SEQUENCES } from '@coffeeandfun/konami-code-detector';
SEQUENCES.classic; // ↑ ↑ ↓ ↓ ← → ← → B A
SEQUENCES.simple; // ↑ ↓ ← →
SEQUENCES.debug; // D E B U G
SEQUENCES.admin; // A D M I NAll sequences are frozen. Pass your own array to sequence if none of them fit.
🧪 Testing
npm test # one run
npm run test:watch # watch mode
npm run test:coverage # coverage reportTests are written with Jest and jsdom. They run under native ESM via --experimental-vm-modules, so jest is imported from @jest/globals inside test files rather than being a global.
📜 License
MIT — see LICENSE.
