bit-twiddler 0.2.1

Cross-platform developer toolbox: bit manipulation, hashing, YAML/JSON/SQL, QR, Markdown, cron, and 40+ more tools — Tauri v2, no Node.js
  // --- JWT Decoder Logic ---
  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.");
    
    // Safely parse Base64Url string bounds
    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');

      // Hook into our robust JSON formatting structures (null key wraps the root map)
      $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);