import wasm from "../../dist/wasm.js"; import { MethodName, WorkerAction } from "../constants.js"; /** * Worker for executing WebClient methods in a separate thread. * * This worker offloads computationally heavy tasks from the main thread by handling * WebClient operations asynchronously. It imports the WASM module and instantiates a * WASM WebClient, then listens for messages from the main thread to perform one of two actions: * * 1. **Initialization (init):** * - The worker receives an "init" message along with user parameters (RPC URL and seed). * - It instantiates the WASM WebClient and calls its create_client method. * - Once initialization is complete, the worker sends a `{ ready: true }` message back to signal * that it is fully initialized. * * 2. **Method Invocation (callMethod):** * - The worker receives a "callMethod" message with a specific method name and arguments. * - It uses a mapping (defined in `methodHandlers`) to route the call to the corresponding WASM WebClient method. * - Complex data is serialized before being sent and deserialized upon return. * - The result (or any error) is then posted back to the main thread. * * The worker uses a message queue to process incoming messages sequentially, ensuring that only one message * is handled at a time. * * Additionally, the worker immediately sends a `{ loaded: true }` message upon script load. This informs the main * thread that the worker script is loaded and ready to receive the "init" message. * * Supported actions (defined in `WorkerAction`): * - "init" : Initialize the WASM WebClient with provided parameters. * - "callMethod" : Invoke a designated method on the WASM WebClient. * * Supported method names are defined in the `MethodName` constant. */ // Global state variables. let wasmWebClient = null; let ready = false; // Indicates if the worker is fully initialized. let messageQueue = []; // Queue for sequential processing. let processing = false; // Flag to ensure one message is processed at a time. // Define a mapping from method names to handler functions. const methodHandlers = { [MethodName.NEW_WALLET]: async (args) => { const [walletStorageModeStr, mutable] = args; const walletStorageMode = wasm.AccountStorageMode.try_from_str(walletStorageModeStr); const wallet = await wasmWebClient.new_wallet(walletStorageMode, mutable); const serializedWallet = await wallet.serialize(); return serializedWallet.buffer; }, [MethodName.NEW_FAUCET]: async (args) => { const [ faucetStorageModeStr, nonFungible, tokenSymbol, decimals, maxSupplyStr, ] = args; const faucetStorageMode = wasm.AccountStorageMode.try_from_str(faucetStorageModeStr); const maxSupply = BigInt(maxSupplyStr); const faucet = await wasmWebClient.new_faucet( faucetStorageMode, nonFungible, tokenSymbol, decimals, maxSupply ); const serializedFaucet = await faucet.serialize(); return serializedFaucet.buffer; }, [MethodName.NEW_TRANSACTION]: async (args) => { const [accountIdStr, serializedTransactionRequest] = args; const accountId = wasm.AccountId.from_hex(accountIdStr); const transactionRequest = wasm.TransactionRequest.deserialize( new Uint8Array(serializedTransactionRequest) ); await wasmWebClient.fetch_and_cache_account_auth_by_pub_key(accountId); const transactionResult = await wasmWebClient.new_transaction( accountId, transactionRequest ); const serializedTransactionResult = await transactionResult.serialize(); return serializedTransactionResult.buffer; }, [MethodName.NEW_MINT_TRANSACTION]: async (args) => { const [targetAccountIdStr, faucetIdStr, noteTypeBytes, amountStr] = args; const targetAccountId = wasm.AccountId.from_hex(targetAccountIdStr); const faucetId = wasm.AccountId.from_hex(faucetIdStr); const noteType = wasm.NoteType.deserialize(new Uint8Array(noteTypeBytes)); const amount = BigInt(amountStr); await wasmWebClient.fetch_and_cache_account_auth_by_pub_key(faucetId); const transactionResult = await wasmWebClient.new_mint_transaction( targetAccountId, faucetId, noteType, amount ); const serializedTransactionResult = await transactionResult.serialize(); return serializedTransactionResult.buffer; }, [MethodName.NEW_CONSUME_TRANSACTION]: async (args) => { const [targetAccountIdStr, noteId] = args; const targetAccountId = wasm.AccountId.from_hex(targetAccountIdStr); await wasmWebClient.fetch_and_cache_account_auth_by_pub_key( targetAccountId ); const transactionResult = await wasmWebClient.new_consume_transaction( targetAccountId, noteId ); const serializedTransactionResult = await transactionResult.serialize(); return serializedTransactionResult.buffer; }, [MethodName.NEW_SEND_TRANSACTION]: async (args) => { const [ senderAccountIdStr, receiverAccountIdStr, faucetIdStr, noteTypeBytes, amountStr, recallHeight, ] = args; const senderAccountId = wasm.AccountId.from_hex(senderAccountIdStr); const receiverAccountId = wasm.AccountId.from_hex(receiverAccountIdStr); const faucetId = wasm.AccountId.from_hex(faucetIdStr); const noteType = wasm.NoteType.deserialize(new Uint8Array(noteTypeBytes)); const amount = BigInt(amountStr); await wasmWebClient.fetch_and_cache_account_auth_by_pub_key( senderAccountId ); const transactionResult = await wasmWebClient.new_send_transaction( senderAccountId, receiverAccountId, faucetId, noteType, amount, recallHeight ); const serializedTransactionResult = await transactionResult.serialize(); return serializedTransactionResult.buffer; }, [MethodName.SUBMIT_TRANSACTION]: async (args) => { // Destructure the arguments. The prover may be undefined. const [serializedTransactionResult, serializedProver] = args; const transactionResult = wasm.TransactionResult.deserialize( new Uint8Array(serializedTransactionResult) ); let prover = undefined; if (serializedProver) { if (serializedProver.startsWith("remote:")) { // For a remote prover, extract the endpoint. // For example, "remote:https://my-custom-endpoint.com" becomes "https://my-custom-endpoint.com" const endpoint = serializedProver.split("remote:")[1]; prover = wasm.TransactionProver.deserialize("remote", endpoint); } else if (serializedProver === "local") { prover = wasm.TransactionProver.deserialize("local"); } else { throw new Error("Invalid prover tag received in worker"); } } // Call the unified submit_transaction method with an optional prover. await wasmWebClient.submit_transaction(transactionResult, prover); return; }, [MethodName.SYNC_STATE]: async () => { const syncSummary = await wasmWebClient.sync_state(); const serializedSyncSummary = await syncSummary.serialize(); return serializedSyncSummary.buffer; }, }; /** * Process a single message event. */ async function processMessage(event) { const { action, args, methodName, requestId } = event.data; try { if (action === WorkerAction.INIT) { const [rpcUrl, seed] = args; // Initialize the WASM WebClient. wasmWebClient = new wasm.WebClient(); await wasmWebClient.create_client(rpcUrl, seed); ready = true; // Signal that the worker is fully initialized. self.postMessage({ ready: true }); return; } else if (action === WorkerAction.CALL_METHOD) { if (!ready) { throw new Error("Worker is not ready. Please initialize first."); } if (!wasmWebClient) { throw new Error("WebClient not initialized in worker."); } // Look up the handler from the mapping. const handler = methodHandlers[methodName]; if (!handler) { throw new Error(`Unsupported method: ${methodName}`); } const result = await handler(args); self.postMessage({ requestId, result }); return; } else { throw new Error(`Unsupported action: ${action}`); } } catch (error) { console.error(`WORKER: Error occurred - ${error}`); self.postMessage({ requestId, error: error }); } } /** * Process messages one at a time from the messageQueue. */ async function processQueue() { if (processing || messageQueue.length === 0) return; processing = true; const event = messageQueue.shift(); try { await processMessage(event); } finally { processing = false; processQueue(); // Process next message in queue. } } // Enqueue incoming messages and process them sequentially. self.onmessage = (event) => { messageQueue.push(event); processQueue(); }; // Immediately signal that the worker script has loaded. // This tells the main thread that the file is fully loaded before sending the "init" message. self.postMessage({ loaded: true });