diff --git a/yarn-project/bb-bench/src/serve.ts b/yarn-project/bb-bench/src/serve.ts index 165b4a5f10a..cfc9d405741 100644 --- a/yarn-project/bb-bench/src/serve.ts +++ b/yarn-project/bb-bench/src/serve.ts @@ -14,37 +14,94 @@ createDebug.enable('*'); // needed for logging in Firefox but not Chrome /* eslint-disable no-console */ -// Function to set up the output element and redirect all console output -function setupConsoleOutput() { - const container = document.createElement('div'); +/** + * Parses console log arguments with %c and corresponding styles. + * @param args - The arguments passed to console methods. + * @returns An HTML string with styles applied. + */ +function parseConsoleArgs(args: any[]): string { + if (typeof args[0] !== 'string') { + // If the first argument is not a string, stringify all arguments + return args.map(arg => escapeHTML(typeof arg === 'string' ? arg : JSON.stringify(arg))).join(' '); + } + + const format = args[0]; + const styleArgs = args.slice(1); + + const parts = format.split('%c'); + const htmlParts: string[] = []; + + for (let i = 0; i < parts.length; i++) { + const text = escapeHTML(parts[i]); + const style = styleArgs[i] || ''; + + if (i === 0) { + // The first part may not have a style + htmlParts.push(text); + } else { + htmlParts.push(`${text}`); + } + } + + return htmlParts.join(''); +} + +/** + * Escapes HTML special characters to prevent XSS. + * @param str - The string to escape. + * @returns The escaped string. + */ +function escapeHTML(str: string): string { + return str.replace(/[&<>"']/g, function (match) { + const escape: { [key: string]: string } = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return escape[match]; + }); +} + +/** + * Sets up the output element and redirects all console output. + */ +function setupConsoleOutput(): void { + const container: HTMLDivElement = document.createElement('div'); container.style.marginBottom = '10px'; document.body.appendChild(container); - const copyButton = document.createElement('button'); + const copyButton: HTMLButtonElement = document.createElement('button'); copyButton.innerText = 'Copy Logs to Clipboard'; copyButton.style.marginBottom = '10px'; container.appendChild(copyButton); - const logContainer = document.createElement('pre'); + const logContainer: HTMLDivElement = document.createElement('div'); logContainer.id = 'logOutput'; logContainer.style.border = '1px solid #ccc'; logContainer.style.padding = '10px'; logContainer.style.maxHeight = '400px'; logContainer.style.overflowY = 'auto'; + logContainer.style.backgroundColor = '#1e1e1e'; // Dark background for better contrast + logContainer.style.color = '#ffffff'; // Default text color + logContainer.style.fontFamily = 'monospace'; // Monospaced font for better readability container.appendChild(logContainer); /** - * Appends a message to the log container and auto-scrolls to the bottom. + * Appends a message to the log container with proper HTML formatting. * @param message - The log message to append. */ function addLogMessage(message: string): void { - logContainer.textContent += `${message}\n`; + const messageElement: HTMLDivElement = document.createElement('div'); + messageElement.innerHTML = message; + logContainer.appendChild(messageElement); logContainer.scrollTop = logContainer.scrollHeight; // Auto-scroll to the bottom } // Add event listener to the copy button copyButton.addEventListener('click', () => { - const logContent: string = logContainer.textContent || ''; // Get text content of log container + const logContent: string = logContainer.innerText || ''; // Get text content of log container navigator.clipboard .writeText(logContent) .then(() => { @@ -64,25 +121,16 @@ function setupConsoleOutput() { // Preserve the original console method const originalMethod = console[method].bind(console); - // Override the console method with type assertions + // Override the console method console[method] = ((...args: any[]) => { - // Process each argument to create a clean message - const message: string = args - .map(arg => - typeof arg === 'string' - ? arg - .replace(/%c/g, '') - .replace(/color:.*?(;|$)/g, '') - .trim() - : JSON.stringify(arg), - ) - .join(' '); + // Parse the console arguments to HTML + const htmlMessage = parseConsoleArgs(args); // Call the original console method with the original arguments originalMethod(...args); - // Append the formatted message to the log container with a prefix indicating the method - addLogMessage(message); + // Append the formatted message to the log container + addLogMessage(htmlMessage); }) as Console[ConsoleMethod]; }); @@ -103,23 +151,21 @@ function overrideCreateDebugLog(addLogMessage: (message: string) => void): void // Call the original createDebug log function originalDebugLog(...args); - // Process the arguments to form a message - const message: string = args - .map(arg => - typeof arg === 'string' - ? arg - .replace(/%c/g, '') - .replace(/color:.*?(;|$)/g, '') - .trim() - : JSON.stringify(arg), - ) - .join(' '); - addLogMessage(message); + // Parse the console arguments to HTML + const htmlMessage = parseConsoleArgs(args); + + // Append the formatted message to the log container + addLogMessage(htmlMessage); }; } +/** + * Converts a hexadecimal string to a Uint8Array. + * @param hex - The hexadecimal string. + * @returns A Uint8Array representation of the hex string. + */ function hexStringToUint8Array(hex: string): Uint8Array { - const length = hex.length / 2; + const length = Math.ceil(hex.length / 2); const uint8Array = new Uint8Array(length); for (let i = 0; i < length; i++) { @@ -130,19 +176,23 @@ function hexStringToUint8Array(hex: string): Uint8Array { return uint8Array; } +// Initialize console output capture and set up event listeners after DOM is loaded document.addEventListener('DOMContentLoaded', function () { setupConsoleOutput(); // Initialize console output capture - const button = document.createElement('button'); + const button: HTMLButtonElement = document.createElement('button'); button.innerText = 'Run Test'; + button.style.marginTop = '10px'; + button.style.padding = '5px 10px'; + button.style.cursor = 'pointer'; button.addEventListener('click', async () => { - logger(`generating circuit and witness...`); + logger('generating circuit and witness...'); const [bytecode1, witness1] = await generateFirstCircuit(); - logger(`done generating circuit and witness. proving...`); + logger('done generating circuit and witness. proving...'); const proverOutput = await proveUltraHonk(bytecode1, witness1); - logger(`done proving. generating second circuit and witness...`); + logger('done proving. generating second circuit and witness...'); const [bytecode2, witness2] = await generateSecondCircuit(proverOutput, FirstVk.keyAsFields); - logger(`done. generating circuit and witness. proving then verifying...`); + logger('done. generating circuit and witness. proving then verifying...'); const verified = await proveThenVerifyUltraHonk(bytecode2, witness2, hexStringToUint8Array(SecondVk.keyAsBytes)); logger(`verified? ${verified}`); });