snowflake-id-decoder / index.html
KaraKaraWitch's picture
Update index.html
e49179c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snowflake ID Decoder</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: #f4f4f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.container {
background: #fff;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 500px;
}
h1 {
color: #1a1a1a;
margin-bottom: 1.5rem;
font-size: 1.75rem;
text-align: center;
}
.input-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #555;
}
input[type="text"], input[type="number"], input[type="datetime-local"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
transition: border-color 0.3s, box-shadow 0.3s;
}
input:focus {
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
outline: none;
}
.output-group {
margin-top: 1.5rem;
border-top: 1px solid #eee;
padding-top: 1.5rem;
}
.output-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.output-label {
font-weight: 600;
}
.output-value {
word-break: break-all;
}
.error {
color: #d9534f;
font-size: 0.875rem;
margin-top: 0.5rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Snowflake ID Converter</h1>
<div class="input-group">
<label for="snowflake-id">Snowflake ID</label>
<input type="text" id="snowflake-id" placeholder="Enter a Snowflake ID...">
<div id="snowflake-error" class="error"></div>
</div>
<div class="output-group">
<h3>Decoded Components</h3>
<div class="output-item">
<span class="output-label">Timestamp (ISO):</span>
<span id="readable-time" class="output-value">-</span>
</div>
<div class="output-item">
<span class="output-label">Timestamp (ms):</span>
<span id="timestamp" class="output-value">-</span>
</div>
<div class="output-item">
<span class="output-label">Machine ID:</span>
<span id="machine-id" class="output-value">-</span>
</div>
<div class="output-item">
<span class="output-label">Sequence ID:</span>
<span id="sequence-id" class="output-value">-</span>
</div>
</div>
<details style="margin-top: 2rem;">
<summary style="cursor: pointer; font-weight: 600; margin-bottom: 1rem;">Manually Construct Snowflake ID</summary>
<div class="input-group">
<label for="construct-timestamp">Timestamp (ms)</label>
<input type="number" id="construct-timestamp" placeholder="Unix timestamp in milliseconds">
</div>
<div class="input-group">
<label for="construct-machine">Machine ID (0-1023)</label>
<input type="number" id="construct-machine" min="0" max="1023" placeholder="e.g., 1">
</div>
<div class="input-group">
<label for="construct-sequence">Sequence ID (0-4095)</label>
<input type="number" id="construct-sequence" min="0" max="4095" placeholder="e.g., 0">
</div>
</details>
</div>
<script>
// NOTE: The Python script used Twitter's epoch (1288834974657), which is a common choice.
// However, Discord and other services use a different epoch. I'm using the one from your script.
// For Discord, this would be 1420070400000.
const SNOWFLAKE_EPOCH = 1288834974657n;
const snowflakeInput = document.getElementById('snowflake-id');
const snowflakeError = document.getElementById('snowflake-error');
const readableTimeOutput = document.getElementById('readable-time');
const timestampOutput = document.getElementById('timestamp');
const machineIdOutput = document.getElementById('machine-id');
const sequenceIdOutput = document.getElementById('sequence-id');
const constructTimestampInput = document.getElementById('construct-timestamp');
const constructMachineInput = document.getElementById('construct-machine');
const constructSequenceInput = document.getElementById('construct-sequence');
/**
* Decodes a Snowflake ID into its parts.
* @param {bigint} snowflakeId The 64-bit snowflake.
* @returns {object|false} The components, or false on error.
*/
function decodeSnowflake(snowflakeId) {
if (snowflakeId < 0n) return false;
// [timestamp (41 bits)][machine-id (10 bits)][sequence-id (12 bits)]
const timestampComponent = snowflakeId >> 22n;
const timestampMs = timestampComponent + SNOWFLAKE_EPOCH;
// Validate that the date is somewhat sane.
if (timestampMs < SNOWFLAKE_EPOCH || timestampMs > BigInt(new Date().getTime()) + 86400000n) { // a day in the future is unlikely
// We can be more permissive if needed, but this is a good sanity check.
}
const machineId = (snowflakeId >> 12n) & 0x3FFn; // 10 bits
const sequenceId = snowflakeId & 0xFFFn; // 12 bits
const date = new Date(Math.floor(Number(timestampMs)));
// Handle potential invalid dates gracefully
const isoString = isNaN(date.getTime()) ? "Invalid Date" : date.toISOString();
return {
readable_time: isoString,
timestamp: timestampMs.toString(),
machine_id: machineId.toString(),
sequence_id: sequenceId.toString()
};
}
/**
* Encodes parts into a Snowflake ID.
* @param {bigint} timestampMs
* @param {bigint} machineId
* @param {bigint} sequenceId
* @returns {bigint|false}
*/
function encodeSnowflake(timestampMs, machineId, sequenceId) {
if (timestampMs <= 0n || machineId < 0n || machineId > 1023n || sequenceId < 0n || sequenceId > 4095n) {
return false;
}
const timeComponent = timestampMs - SNOWFLAKE_EPOCH;
if (timeComponent < 0n) return false; // Cannot generate IDs before the epoch
return (timeComponent << 22n) | (machineId << 12n) | sequenceId;
}
// --- EVENT LISTENERS ---
function handleSnowflakeDecode() {
const value = snowflakeInput.value;
if (!value) {
clearDecodedOutputs();
snowflakeError.textContent = '';
return;
}
if (!/^\d+$/.test(value)) {
snowflakeError.textContent = 'Invalid input. Snowflake IDs must be numbers.';
clearDecodedOutputs();
return;
}
snowflakeError.textContent = '';
try {
const snowflakeId = BigInt(value);
const components = decodeSnowflake(snowflakeId);
if (!components) {
snowflakeError.textContent = 'Invalid Snowflake ID.'; // e.g., timestamp before epoch
clearDecodedOutputs();
return;
}
readableTimeOutput.textContent = components.readable_time;
timestampOutput.textContent = components.timestamp;
machineIdOutput.textContent = components.machine_id;
sequenceIdOutput.textContent = components.sequence_id;
// Populate the construction fields
constructTimestampInput.value = components.timestamp;
constructMachineInput.value = components.machine_id;
constructSequenceInput.value = components.sequence_id;
} catch (e) {
console.error(e)
snowflakeError.textContent = 'An unexpected error occurred.';
clearDecodedOutputs();
}
}
function handleConstructSnowflake() {
const timestamp = constructTimestampInput.value;
const machine = constructMachineInput.value;
const sequence = constructSequenceInput.value;
if (timestamp && machine && sequence) {
try {
const timestampBig = BigInt(timestamp);
const machineBig = BigInt(machine);
const sequenceBig = BigInt(sequence);
const newSnowflake = encodeSnowflake(timestampBig, machineBig, sequenceBig);
if (newSnowflake === false) {
snowflakeError.textContent = 'Component values are out of valid range.';
} else {
snowflakeInput.value = newSnowflake.toString();
handleSnowflakeDecode(); // Trigger an immediate decode of the new ID
}
} catch (e) {
snowflakeError.textContent = 'An unexpected error during construction.';
}
}
}
function clearDecodedOutputs() {
readableTimeOutput.textContent = '-';
timestampOutput.textContent = '-';
machineIdOutput.textContent = '-';
sequenceIdOutput.textContent = '-';
}
// Attach listeners
snowflakeInput.addEventListener('input', handleSnowflakeDecode);
constructTimestampInput.addEventListener('input', handleConstructSnowflake);
constructMachineInput.addEventListener('input', handleConstructSnowflake);
constructSequenceInput.addEventListener('input', handleConstructSnowflake);
</script>
</body>
</html>