Skip to content

CustomTabs examples #2308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
iliakan opened this issue Apr 15, 2025 · 2 comments
Open

CustomTabs examples #2308

iliakan opened this issue Apr 15, 2025 · 2 comments

Comments

@iliakan
Copy link

iliakan commented Apr 15, 2025

Here're two quick examples of formatting functions for custom tabs.
Just wrote them, tested a tiny bit =)

RawBody

  • No headers
  • Non-printable characters become .
  • Supports mixed binary + UTF8
/**
 * Formats a Uint8Array or string containing mixed binary data and UTF-8 text
 * Non-printable characters are replaced with dots
 * 
 * @param {Uint8Array|string} data - The binary data to format
 * @returns {string} Text representation of the data with non-printable characters as dots
 */
function formatBinaryData(data) {
  if (!data || data.length === 0) {
    return '';
  }
  
  // Convert string input to Uint8Array if needed
  if (typeof data === 'string') {
    // Convert string to array of character codes
    const charCodes = new Uint8Array(data.length);
    for (let i = 0; i < data.length; i++) {
      charCodes[i] = data.charCodeAt(i) & 0xFF; // Get the byte value
    }
    data = charCodes;
  }


  // Process the Uint8Array manually without TextDecoder
  let result = '';
  let i = 0;
  
  while (i < data.length) {
    const byte = data[i];
    
    // Check if this is the start of a UTF-8 multi-byte sequence
    if ((byte & 0xE0) === 0xC0 && i + 1 < data.length) { // 2-byte sequence
      // Ensure the continuation byte is valid
      if ((data[i + 1] & 0xC0) === 0x80) {
        // Decode 2-byte UTF-8 sequence manually
        const codePoint = ((byte & 0x1F) << 6) | (data[i + 1] & 0x3F);
        if (codePoint >= 0x80) { // Valid code point
          result += String.fromCharCode(codePoint);
          i += 2;
          continue;
        }
      }
      // Invalid sequence
      result += '.';
      i++;
    } else if ((byte & 0xF0) === 0xE0 && i + 2 < data.length) { // 3-byte sequence
      // Ensure the continuation bytes are valid
      if ((data[i + 1] & 0xC0) === 0x80 && (data[i + 2] & 0xC0) === 0x80) {
        // Decode 3-byte UTF-8 sequence manually
        const codePoint = ((byte & 0x0F) << 12) | 
                         ((data[i + 1] & 0x3F) << 6) | 
                         (data[i + 2] & 0x3F);
        if (codePoint >= 0x800 && (codePoint < 0xD800 || codePoint > 0xDFFF)) { // Valid code point
          result += String.fromCharCode(codePoint);
          i += 3;
          continue;
        }
      }
      // Invalid sequence
      result += '.';
      i++;
    } else if ((byte & 0xF8) === 0xF0 && i + 3 < data.length) { // 4-byte sequence
      // Ensure the continuation bytes are valid
      if ((data[i + 1] & 0xC0) === 0x80 && 
          (data[i + 2] & 0xC0) === 0x80 && 
          (data[i + 3] & 0xC0) === 0x80) {
        // Decode 4-byte UTF-8 sequence manually
        const codePoint = ((byte & 0x07) << 18) | 
                         ((data[i + 1] & 0x3F) << 12) | 
                         ((data[i + 2] & 0x3F) << 6) | 
                         (data[i + 3] & 0x3F);
        if (codePoint >= 0x10000 && codePoint <= 0x10FFFF) { // Valid code point
          // For code points above 0xFFFF, we need to use surrogate pairs
          const highSurrogate = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800;
          const lowSurrogate = ((codePoint - 0x10000) % 0x400) + 0xDC00;
          result += String.fromCharCode(highSurrogate, lowSurrogate);
          i += 4;
          continue;
        }
      }
      // Invalid sequence
      result += '.';
      i++;
    } else if (byte >= 32 && byte <= 126) { // ASCII printable
      result += String.fromCharCode(byte);
      i++;
    } else if (byte === 10) { // Newline character (\n)
      result += '\n';
      i++;
    } else { // Non-printable
      result += '.';
      i++;
    }
  }
  
  return result;
}


function onRequest(context, url, request) {

  let rawRequest = '';

  try {
    // If it's an array of bytes, decode as UTF-8 (like raw text)
    try {
      const decoded = formatBinaryData(request.rawBody);
      rawRequest += decoded;
    } catch(e) {
      rawRequest += String(request.rawBody);
    }
  } catch (e) {
    rawRequest += e.message;
  }


  // Add to custom previewer tab
  request.customPreviewerTabs["RawBody"] = rawRequest;

  return request;
}

function onResponse(context, url, request, response) {
  response.customPreviewerTabs['RawBody'] = formatBinaryData(response.rawBody);
  return response;
}

EventSource

Makes it JSON (more readable)

function formatEventStream(rawStream) {
  
  const lines = rawStream.split('\n');

  let events = [];
  for (const line of lines) {
    if (line.startsWith('event: ')) {
      const event = line.slice(7);
      events.push({type: event, data: []});
    } else if (line.startsWith('data: ')) {
      let data = line.slice(6);

      try {
        data = JSON.parse(data);
      } catch (e) {
        // ignore 
      }
      
      if (data == '[DONE]') break;
      events.at(-1).data.push(data);
    }
  }

  return events;
}

async function onResponse(context, url, request, response) {

  if (!response.headers["Content-Type"] || !response.headers["Content-Type"].includes('event-stream')) {
    return response;
  }
  
  try {
    const formatted = formatEventStream(response.rawBody);
    response.customPreviewerTabs['EventStream'] = JSON.stringify(formatted, null, 2);
  } catch(e) {
    response.customPreviewerTabs['EventStream'] = e.message;
  }
  return response;
}
@NghiaTranUIT
Copy link
Member

Thanks, but it's too complicated to convert from Uint8Array to String. Most of the time, your Body is just a string or JSON. You can serialize it to a string easily by using JSON.stringify()

If it's Uint8Array, don't try to convert it to text because it will contain non-reading chars. Just add <raw body> and continue with the Header

@iliakan
Copy link
Author

iliakan commented Apr 16, 2025

In my case, I have a mixed binary and string output because the format is unknown, it looks like a protobuf without description, but protobuf viewer fails to display it well.

So this formatter aims to output it as good as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants