const $jwtInput = $('#jwt-input');
window.attachToolHistory('#jwt-input', 'jwt');
const $jwtStatus = $('#jwt-status');
const $jwtOutHeader = $('#jwt-output-header');
const $jwtOutPayload = $('#jwt-output-payload');
const $jwtOutSignature = $('#jwt-output-signature');
const $jwtSearch = $('#jwt-search');
const $jwtClearBtn = $('#jwt-clear-btn');
const $jwtCopyBtn = $('#jwt-copy-btn');
const $jwtExpBadge = $('#jwt-exp-badge');
const $jwtVerifyAlg = $('#jwt-verify-alg');
const $jwtVerifyKey = $('#jwt-verify-key');
const $jwtVerifyBtn = $('#jwt-verify-btn');
const $jwtVerifyResult = $('#jwt-verify-result');
let lastDecoded = null;
let lastRawToken = '';
const resetVerifyResult = () => {
$jwtVerifyResult.addClass('hidden').removeClass(
'bg-emerald-500/20 text-emerald-400 bg-rose-500/20 text-rose-400 bg-gray-700/40 text-gray-400'
);
};
const updateExpBadge = (payload) => {
if (!payload || typeof payload.exp !== 'number') {
$jwtExpBadge.addClass('hidden');
return;
}
const expDate = new Date(payload.exp * 1000);
const isExpired = payload.exp < Date.now() / 1000;
$jwtExpBadge
.removeClass('hidden text-rose-400 text-gray-500')
.addClass(isExpired ? 'text-rose-400' : 'text-gray-500')
.text((isExpired ? '⚠ Expired ' : 'Expires ') + expDate.toISOString());
};
$jwtClearBtn.on('click', function() {
$jwtInput.val('').trigger('input');
$jwtSearch.val('');
});
$jwtSearch.on('input', function() {
const q = $(this).val();
if(q) {
$('#jwt-tree-view details').prop('open', true);
}
});
$jwtSearch.on('keydown', function(e) {
if(e.key === 'Enter') {
e.preventDefault();
const q = $(this).val();
if(q) {
window.find(q, false, false, true, false, true, false);
}
}
});
const decodeJWT = (token) => {
const parts = token.split('.');
if(parts.length !== 3) throw new Error("Invalid JWT Format: Must have 3 parts.");
const b64DecodeUnicode = str => {
const base64 = str.replace(/-/g, '+').replace(/_/g, '/');
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '=');
return decodeURIComponent(
atob(padded).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')
);
};
return {
header: JSON.parse(b64DecodeUnicode(parts[0])),
payload: JSON.parse(b64DecodeUnicode(parts[1])),
signature: parts[2]
};
};
const updateJWTUI = () => {
const raw = $jwtInput.val().trim();
lastRawToken = raw;
resetVerifyResult();
if (!raw) {
$jwtStatus.text("Ready").removeClass('bg-red-500/20 text-red-400 bg-green-500/20 text-green-400').addClass('bg-gray-800 text-gray-500');
$jwtOutHeader.html('');
$jwtOutPayload.html('');
$jwtOutSignature.text('');
$jwtExpBadge.addClass('hidden');
$jwtVerifyAlg.text('');
lastDecoded = null;
return;
}
try {
const decoded = decodeJWT(raw);
lastDecoded = decoded;
$jwtStatus.text("Valid Token").removeClass('bg-gray-800 text-gray-500 bg-red-500/20 text-red-400').addClass('bg-green-500/20 text-green-400');
$jwtOutHeader.html(createTreeNode(null, decoded.header, true));
$jwtOutPayload.html(createTreeNode(null, decoded.payload, true));
$jwtOutSignature.text(decoded.signature);
updateExpBadge(decoded.payload);
$jwtVerifyAlg.text(decoded.header && decoded.header.alg ? `(${decoded.header.alg})` : '');
} catch (e) {
$jwtStatus.text("Invalid Token").removeClass('bg-gray-800 text-gray-500 bg-green-500/20 text-green-400').addClass('bg-red-500/20 text-red-400');
$jwtOutHeader.html('');
$jwtOutPayload.html('');
$jwtOutSignature.text('');
$jwtExpBadge.addClass('hidden');
$jwtVerifyAlg.text('');
lastDecoded = null;
}
};
$jwtCopyBtn.on('click', function() {
if (lastDecoded) window.copyToClipboard(JSON.stringify(lastDecoded.payload, null, 2), $(this));
});
$jwtVerifyBtn.on('click', async () => {
const key = $jwtVerifyKey.val();
if (!lastDecoded || !lastRawToken) {
resetVerifyResult();
$jwtVerifyResult.removeClass('hidden').addClass('bg-gray-700/40 text-gray-400').text('Decode a token first.');
return;
}
if (!key) {
resetVerifyResult();
$jwtVerifyResult.removeClass('hidden').addClass('bg-gray-700/40 text-gray-400').text('Enter a secret or public key.');
return;
}
try {
const isValid = await window.tauriApi.verifyJwt(lastRawToken, key);
resetVerifyResult();
if (isValid) {
$jwtVerifyResult.removeClass('hidden').addClass('bg-emerald-500/20 text-emerald-400').text('✅ Signature valid');
} else {
$jwtVerifyResult.removeClass('hidden').addClass('bg-rose-500/20 text-rose-400').text('❌ Invalid signature');
}
} catch (e) {
resetVerifyResult();
$jwtVerifyResult.removeClass('hidden').addClass('bg-rose-500/20 text-rose-400').text('Error: ' + e);
}
});
$jwtInput.on('input', updateJWTUI);