This commit is contained in:
mfahampshire
2026-03-27 12:50:25 +00:00
parent c07ef0253d
commit 4d7213aea8
5 changed files with 416 additions and 269 deletions
+98 -77
View File
@@ -3,100 +3,121 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nym MixFetch Demo</title>
<title>Nym MixFetch Dev</title>
<script src="bootstrap.js"></script>
</head>
<body>
<h1>Mix Fetch Demo</h1>
<h1>MixFetch Dev</h1>
<fieldset id="startup-controls">
<legend>MixFetch Configuration</legend>
<p>
You can either use the default Gateway/NR combo run by us, or have
MixFetch choose a random one on startup.
</p>
<div style="margin-bottom: 10px">
<label>
<input type="radio" name="gateway-mode" value="default" checked />
Default Gateway (q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1)
</label>
<legend>Connection</legend>
<div>
<label><input type="radio" name="gateway-mode" value="default" checked />
Default Gateway (q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1)</label>
</div>
<div style="margin-bottom: 10px">
<label>
<input type="radio" name="gateway-mode" value="random" />
Random Gateway
</label>
<div>
<label><input type="radio" name="gateway-mode" value="random" />
Random Gateway</label>
</div>
<div style="margin-top: 8px">
<button id="start-mixfetch">Start MixFetch</button>
<span id="mixfetch-status" style="margin-left: 10px; color: gray">Not started</span>
</div>
<button id="start-mixfetch">Start MixFetch</button>
<span id="mixfetch-status" style="margin-left: 10px; color: gray"
>Not started</span
>
</fieldset>
<hr />
<fieldset id="fetch-controls" disabled>
<legend>Fetch Controls</legend>
<legend>Quick Fetch</legend>
<div>
<label>Target Host 1: </label>
<input
type="text"
size="60"
id="fetch_payload_1"
value="https://api.ipify.org?format=json"
/>
<button id="fetch-button-1">Fetch 1</button>
<input type="text" size="55" id="fetch_payload_1" value="https://api.ipify.org?format=json" />
<button id="fetch-button-1">GET</button>
</div>
<div>
<label>Target Host 2: </label>
<input
type="text"
size="60"
id="fetch_payload_2"
value="https://api6.ipify.org?format=json"
/>
<button id="fetch-button-2">Fetch 2</button>
<div style="margin-top: 4px">
<input type="text" size="55" id="fetch_payload_2" value="https://api6.ipify.org?format=json" />
<button id="fetch-button-2">GET</button>
</div>
<p>
Note: if you're hammering these endpoints and you start to get timeouts
(or you've been reloading the page a lot) then change the target hosts
to e.g. http://ipv4.icanhazip.com and https://ipv6.icanhazip.com/
</p>
<pre id="fetch-log" style="background: #f5f5f5; padding: 8px; margin-top: 8px; max-height: 150px; overflow-y: auto; font-size: 0.85em; white-space: pre-wrap; display: none"></pre>
<hr />
<h3>Stress Test</h3>
<div>
<button id="fetch-10-concurrent">
Send 10 Concurrent Requests (posts/1-10)
</button>
<p>
This does what is says on the tin - sends 10 fetch requests to
https://jsonplaceholder.typicode.com/posts/ 1 through 10
<label>Requests:</label>
<input type="number" id="stress-test-count" value="20" min="1" max="200" style="width: 60px" />
<label>Mode:</label>
<select id="stress-test-mode">
<option value="uniform">Uniform (same URL, incremented ID)</option>
<option value="mixed" selected>Mixed sizes</option>
<option value="drip">Slow drip (timeout boundary)</option>
</select>
</div>
<div id="stress-uniform-opts" style="display: none; margin-top: 8px; padding: 8px; background: #f9f9f9">
<div>
<label>Base URL:</label>
<input type="text" size="50" id="stress-test-url" value="https://jsonplaceholder.typicode.com/posts/" />
</div>
<p style="margin: 4px 0 0; font-size: 0.85em; color: #666">
Request ID (1..N) appended to the URL. All requests are the same size.
</p>
</div>
<hr />
<div>
<label>POST URL: </label>
<input
type="text"
size="60"
id="post_url"
value="https://jsonplaceholder.typicode.com/posts"
/>
</div>
<div>
<label>POST Body (JSON): </label>
<textarea id="post_body" rows="3" cols="60">
{"title": "Test Post", "body": "Hello from MixFetch!", "userId": 1}</textarea
>
</div>
<div>
<button id="post-button">Send POST Request</button>
</div>
<p>Do a POST and get it echoed back</p>
</fieldset>
<hr />
<p>
<span id="output"></span>
</p>
<div id="stress-mixed-opts" style="margin-top: 8px; padding: 8px; background: #f9f9f9">
<p style="margin: 0 0 4px; font-size: 0.85em; color: #666">
Each request randomly assigned a size via <code>httpbin.org/bytes/{n}</code>.
Tests how the mixnet handles asymmetric payload sizes concurrently.
</p>
<table style="font-size: 0.9em; border-collapse: collapse">
<tr><td style="padding: 1px 10px 1px 0"><b>tiny</b></td><td>128 B</td></tr>
<tr><td style="padding: 1px 10px 1px 0"><b>small</b></td><td>1 KB</td></tr>
<tr><td style="padding: 1px 10px 1px 0"><b>medium</b></td><td>10 KB</td></tr>
<tr><td style="padding: 1px 10px 1px 0"><b>large</b></td><td>100 KB</td></tr>
<tr><td style="padding: 1px 10px 1px 0"><b>xlarge</b></td><td>1 MB</td></tr>
</table>
</div>
<div id="stress-drip-opts" style="display: none; margin-top: 8px; padding: 8px; background: #f9f9f9">
<p style="margin: 0 0 4px; font-size: 0.85em; color: #666">
Uses <code>httpbin.org/drip</code> to slowly drip bytes over a set duration,
keeping connections alive for a controlled time. Profiles are designed to
straddle the Go timeout boundary &mdash; some will finish just in time,
others will be mid-transfer when the timeout fires. This is how we
reproduce the Go runtime panic (see DEBUGGING_NOTES.md).
</p>
<table style="font-size: 0.9em; border-collapse: collapse">
<tr>
<td style="padding: 1px 10px 1px 0"><b>safe</b></td>
<td>30s</td>
<td style="padding-left: 10px; color: #666">well under 60s timeout</td>
</tr>
<tr>
<td style="padding: 1px 10px 1px 0"><b>boundary</b></td>
<td>55s</td>
<td style="padding-left: 10px; color: #666">55s server + ~10s mixnet latency = over</td>
</tr>
<tr>
<td style="padding: 1px 10px 1px 0"><b>over</b></td>
<td>65s</td>
<td style="padding-left: 10px; color: #666">exceeds 60s timeout on its own</td>
</tr>
<tr>
<td style="padding: 1px 10px 1px 0"><b>slow-start</b></td>
<td>10s delay + 50s</td>
<td style="padding-left: 10px; color: #666">late first byte, then long transfer</td>
</tr>
</table>
</div>
<div style="margin-top: 8px">
<label>Go timeout (ms):</label>
<input type="number" id="stress-go-timeout" value="60000" min="500" max="120000" style="width: 80px" />
</div>
<div style="margin-top: 8px">
<button id="stress-test-button">Run Stress Test</button>
<span id="stress-test-status" style="margin-left: 10px"></span>
</div>
<div id="stress-tracker" style="margin-top: 8px; font-family: monospace; font-size: 0.85em; display: none"></div>
</fieldset>
</body>
</html>
+286 -171
View File
@@ -12,225 +12,340 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ─── Worker client ──────────────────────────────────────────────────────────
class WebWorkerClient {
worker = null;
worker = null;
constructor() {
this.worker = new Worker('./worker.js');
constructor() {
this.worker = new Worker("./worker.js");
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'DisplayString':
const { rawString } = ev.data.args;
displayReceivedRawString(rawString)
break;
case 'Log':
const { message, level } = ev.data.args;
displayLog(message, level);
break;
case 'MixFetchReady':
onMixFetchReady();
break;
case 'MixFetchError':
const { error } = ev.data.args;
onMixFetchError(error);
break;
}
}
};
}
startMixFetch = (preferredGateway) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
this.worker.onmessage = (ev) => {
if (!ev.data || !ev.data.kind) return;
switch (ev.data.kind) {
case "DisplayString":
appendFetchLog(ev.data.args.rawString);
console.log("[mixfetch response]", ev.data.args.rawString);
break;
case "Log": {
const { message, level } = ev.data.args;
const fn = level === "error" ? console.error
: level === "warn" ? console.warn
: console.log;
fn(`[worker/${level}]`, message);
break;
}
case "MixFetchReady":
onMixFetchReady();
break;
case "MixFetchError":
onMixFetchError(ev.data.args.error);
break;
case "StressTestFetchResult":
onStressTestFetchResult(ev.data.args);
break;
}
};
}
this.worker.postMessage({
kind: 'StartMixFetch',
args: {
preferredGateway,
},
});
}
doFetch = (target) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'FetchPayload',
args: {
target,
},
});
}
doPost = (url, body) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'PostPayload',
args: {
url,
body,
},
});
startMixFetch = (preferredGateway) => {
this.worker.postMessage({ kind: "StartMixFetch", args: { preferredGateway } });
};
doFetch = (target) => {
this.worker.postMessage({ kind: "FetchPayload", args: { target } });
};
setGoTimeout = (timeoutMs) => {
this.worker.postMessage({ kind: "SetGoTimeout", args: { timeoutMs } });
};
doStressTest = (requests) => {
for (const req of requests) {
this.worker.postMessage({
kind: "StressTestFetch",
args: { id: req.id, url: req.url, label: req.label },
});
}
};
}
let client = null;
// ─── Startup ────────────────────────────────────────────────────────────────
let client = null;
const DEFAULT_GATEWAY = "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1";
async function main() {
client = new WebWorkerClient();
client = new WebWorkerClient();
const startButton = document.querySelector('#start-mixfetch');
startButton.onclick = function () {
const gatewayMode = document.querySelector('input[name="gateway-mode"]:checked').value;
const preferredGateway = gatewayMode === 'default' ? DEFAULT_GATEWAY : undefined;
document.querySelector("#start-mixfetch").onclick = () => {
const gatewayMode = document.querySelector('input[name="gateway-mode"]:checked').value;
const preferredGateway = gatewayMode === "default" ? DEFAULT_GATEWAY : undefined;
startButton.disabled = true;
document.querySelectorAll('input[name="gateway-mode"]').forEach(r => r.disabled = true);
updateStatus('Starting...', 'orange');
document.querySelector("#start-mixfetch").disabled = true;
document.querySelectorAll('input[name="gateway-mode"]').forEach((r) => (r.disabled = true));
updateStatus("mixfetch-status", "Starting...", "orange");
displayLog(`Starting MixFetch with ${gatewayMode} gateway${preferredGateway ? ` (${preferredGateway})` : ''}...`, 'info');
client.startMixFetch(preferredGateway);
console.log(`Starting MixFetch (${gatewayMode} gateway${preferredGateway ? `: ${preferredGateway}` : ""})...`);
client.startMixFetch(preferredGateway);
};
document.querySelector("#fetch-button-1").onclick = () => doFetch(1);
document.querySelector("#fetch-button-2").onclick = () => doFetch(2);
const stressModeSelect = document.getElementById("stress-test-mode");
stressModeSelect.onchange = function () {
document.getElementById("stress-uniform-opts").style.display = this.value === "uniform" ? "block" : "none";
document.getElementById("stress-mixed-opts").style.display = this.value === "mixed" ? "block" : "none";
document.getElementById("stress-drip-opts").style.display = this.value === "drip" ? "block" : "none";
};
document.querySelector("#stress-test-button").onclick = () => {
const count = parseInt(document.getElementById("stress-test-count").value, 10);
const mode = document.getElementById("stress-test-mode").value;
const goTimeoutMs = parseInt(document.getElementById("stress-go-timeout").value, 10);
document.querySelector("#stress-test-button").disabled = true;
updateStatus("stress-test-status", "Running...", "orange");
client.setGoTimeout(goTimeoutMs);
const requests = generateStressRequests(count, mode);
stressTest = {
count,
mode,
startTime: performance.now(),
results: [],
completionOrder: [],
profiles: {},
};
for (const req of requests) {
stressTest.profiles[req.id] = { label: req.label, bytes: req.bytes };
}
const fetchButton1 = document.querySelector('#fetch-button-1');
fetchButton1.onclick = function () {
doFetch(1);
initStressTracker(requests);
console.log(`%c=== STRESS TEST: ${count} requests, ${mode} mode, timeout=${goTimeoutMs}ms ===`, "font-weight: bold");
if (mode === "mixed" || mode === "drip") {
const breakdown = {};
for (const req of requests) breakdown[req.label] = (breakdown[req.label] || 0) + 1;
console.log("Profiles:", breakdown);
}
const fetchButton2 = document.querySelector('#fetch-button-2');
fetchButton2.onclick = function () {
doFetch(2);
}
const fetch10Button = document.querySelector('#fetch-10-concurrent');
fetch10Button.onclick = function () {
doFetch10Concurrent();
}
const postButton = document.querySelector('#post-button');
postButton.onclick = function () {
doPost();
}
client.doStressTest(requests);
};
}
function updateStatus(text, color) {
const status = document.getElementById('mixfetch-status');
status.textContent = text;
status.style.color = color;
// ─── UI helpers ─────────────────────────────────────────────────────────────
function updateStatus(elementId, text, color) {
const el = document.getElementById(elementId);
el.textContent = text;
el.style.color = color;
}
function onMixFetchReady() {
updateStatus('Ready', 'green');
document.getElementById('fetch-controls').disabled = false;
displayLog('MixFetch is ready!', 'info');
updateStatus("mixfetch-status", "Ready", "green");
document.getElementById("fetch-controls").disabled = false;
console.log("%cMixFetch ready!", "color: green; font-weight: bold");
}
function onMixFetchError(error) {
updateStatus('Error: ' + error, 'red');
document.querySelector('#start-mixfetch').disabled = false;
document.querySelectorAll('input[name="gateway-mode"]').forEach(r => r.disabled = false);
displayLog('MixFetch error: ' + error, 'error');
updateStatus("mixfetch-status", "Error: " + error, "red");
document.querySelector("#start-mixfetch").disabled = false;
document.querySelectorAll('input[name="gateway-mode"]').forEach((r) => (r.disabled = false));
console.error("MixFetch error:", error);
}
// ─── Quick fetch ────────────────────────────────────────────────────────────
function appendFetchLog(text) {
const log = document.getElementById("fetch-log");
log.style.display = "block";
const ts = new Date().toISOString().substr(11, 12);
log.textContent += `${ts} ${text}\n`;
log.scrollTop = log.scrollHeight;
}
async function doFetch(id) {
const payload = document.getElementById(`fetch_payload_${id}`).value;
await client.doFetch(payload)
displaySend(`[${id}] clicked the button and the payload is: ${payload}...`);
const url = document.getElementById(`fetch_payload_${id}`).value;
appendFetchLog(`GET ${url}`);
console.log(`GET ${url}`);
await client.doFetch(url);
}
async function doFetch10Concurrent() {
const baseUrl = 'https://jsonplaceholder.typicode.com/posts/';
displaySend('Starting 10 concurrent requests to posts/1-10...');
// ─── Stress test ────────────────────────────────────────────────────────────
const requests = [];
for (let i = 1; i <= 10; i++) {
const url = `${baseUrl}${i}`;
displaySend(`[${i}] Sending request to ${url}`);
requests.push(client.doFetch(url));
const STRESS_PROFILES = [
{ label: "tiny", bytes: 128 },
{ label: "small", bytes: 1024 },
{ label: "medium", bytes: 10240 },
{ label: "large", bytes: 102400 },
{ label: "xlarge", bytes: 1048576 },
];
const DRIP_PROFILES = [
{ label: "safe", dripDuration: 30, dripDelay: 0, dripBytes: 100 },
{ label: "boundary", dripDuration: 55, dripDelay: 0, dripBytes: 100 },
{ label: "over", dripDuration: 65, dripDelay: 0, dripBytes: 100 },
{ label: "slow-start", dripDuration: 50, dripDelay: 10, dripBytes: 100 },
];
function generateStressRequests(count, mode) {
const requests = [];
if (mode === "uniform") {
const baseUrl = document.getElementById("stress-test-url").value;
for (let i = 1; i <= count; i++) {
requests.push({ id: i, url: `${baseUrl}${i}`, label: "uniform", bytes: null });
}
} else if (mode === "drip") {
for (let i = 1; i <= count; i++) {
const p = DRIP_PROFILES[Math.floor(Math.random() * DRIP_PROFILES.length)];
requests.push({
id: i,
url: `https://httpbin.org/drip?duration=${p.dripDuration}&numbytes=${p.dripBytes}&delay=${p.dripDelay}&code=200`,
label: p.label,
bytes: p.dripBytes,
});
}
} else {
for (let i = 1; i <= count; i++) {
const p = STRESS_PROFILES[Math.floor(Math.random() * STRESS_PROFILES.length)];
requests.push({
id: i,
url: `https://httpbin.org/bytes/${p.bytes}`,
label: p.label,
bytes: p.bytes,
});
}
}
return requests;
}
let stressTest = null;
// Per-profile live tracker state
let trackerData = {};
function initStressTracker(requests) {
const tracker = document.getElementById("stress-tracker");
tracker.style.display = "block";
trackerData = {};
// Count how many of each profile were sent
for (const req of requests) {
if (!trackerData[req.label]) {
trackerData[req.label] = { sent: 0, ok: 0, fail: 0, times: [] };
}
trackerData[req.label].sent++;
}
renderTracker();
}
function renderTracker() {
const tracker = document.getElementById("stress-tracker");
const rows = Object.entries(trackerData).map(([label, d]) => {
const pending = d.sent - d.ok - d.fail;
const avg = d.times.length > 0
? (d.times.reduce((a, b) => a + b, 0) / d.times.length).toFixed(1) + "s"
: "-";
const max = d.times.length > 0 ? Math.max(...d.times).toFixed(1) + "s" : "-";
const min = d.times.length > 0 ? Math.min(...d.times).toFixed(1) + "s" : "-";
let status = "";
if (d.ok > 0) status += `<span style="color: green">${d.ok} ok</span>`;
if (d.fail > 0) status += `${status ? " " : ""}<span style="color: red">${d.fail} fail</span>`;
if (pending > 0) status += `${status ? " " : ""}<span style="color: gray">${pending} pending</span>`;
let timing = "";
if (d.times.length > 0) {
timing = `avg ${avg} / min ${min} / max ${max}`;
}
await Promise.all(requests);
displaySend('All 10 concurrent requests dispatched!');
return `<tr>
<td style="padding: 2px 8px; font-weight: bold">${label}</td>
<td style="padding: 2px 8px">${d.sent}</td>
<td style="padding: 2px 8px">${status}</td>
<td style="padding: 2px 8px; color: #666">${timing}</td>
</tr>`;
});
tracker.innerHTML = `<table style="border-collapse: collapse">
<tr style="border-bottom: 1px solid #ccc">
<th style="padding: 2px 8px; text-align: left">profile</th>
<th style="padding: 2px 8px; text-align: left">sent</th>
<th style="padding: 2px 8px; text-align: left">status</th>
<th style="padding: 2px 8px; text-align: left">timing</th>
</tr>
${rows.join("")}
</table>`;
}
async function doPost() {
const url = document.getElementById('post_url').value;
const body = document.getElementById('post_body').value;
function onStressTestFetchResult(result) {
if (!stressTest) return;
displaySend(`[POST] Sending POST request to ${url}`);
displaySend(`[POST] Body: ${body}`);
const profile = stressTest.profiles[result.id];
result.label = profile ? profile.label : "?";
await client.doPost(url, body);
}
stressTest.results.push(result);
stressTest.completionOrder.push(result.id);
/**
* Display log messages from MixFetch. Colors based on level.
*
* @param {string} message
* @param {string} level - 'info', 'error', 'warn', or 'debug'
*/
function displayLog(message, level) {
let timestamp = new Date().toISOString().substr(11, 12);
// Update tracker
const td = trackerData[result.label];
if (td) {
if (result.ok) {
td.ok++;
td.times.push(parseFloat(result.elapsed));
} else {
td.fail++;
}
renderTracker();
}
const colors = {
info: 'gray',
error: 'red',
warn: 'orange',
debug: 'purple',
};
const progress = `${stressTest.results.length}/${stressTest.count}`;
const tag = `#${result.id} ${result.label}`;
let logDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', `color: ${colors[level] || 'gray'}`);
let paragraphContent = document.createTextNode(timestamp + ' [' + level.toUpperCase() + '] ' + message);
paragraph.appendChild(paragraphContent);
if (result.ok) {
console.log(`%c[${tag}] ${result.status} OK ${result.elapsed}s ${result.textLength}B (${progress})`, "color: green");
} else {
console.error(`[${tag}] FAIL ${result.elapsed}s ${result.error} (${progress})`);
}
logDiv.appendChild(paragraph);
document.getElementById('output').appendChild(logDiv);
}
updateStatus("stress-test-status", progress, "orange");
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
// Summary on completion
if (stressTest.results.length === stressTest.count) {
const totalElapsed = ((performance.now() - stressTest.startTime) / 1000).toFixed(2);
const succeeded = stressTest.results.filter((r) => r.ok).length;
const failed = stressTest.results.filter((r) => !r.ok).length;
let sendDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
paragraph.appendChild(paragraphContent);
console.log(`%c=== COMPLETE: ${totalElapsed}s | OK ${succeeded}/${stressTest.count} | Failed ${failed}/${stressTest.count} ===`, "font-weight: bold");
console.log("Completion order:", stressTest.completionOrder);
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
// Per-profile breakdown in console
const profileList = stressTest.mode === "drip" ? DRIP_PROFILES : STRESS_PROFILES;
if (stressTest.mode === "mixed" || stressTest.mode === "drip") {
const table = [];
for (const profile of profileList) {
const matching = stressTest.results.filter((r) => r.label === profile.label);
if (matching.length === 0) continue;
const ok = matching.filter((r) => r.ok).length;
const times = matching.filter((r) => r.ok).map((r) => parseFloat(r.elapsed));
const avg = times.length > 0 ? (times.reduce((a, b) => a + b, 0) / times.length).toFixed(2) : "-";
const max = times.length > 0 ? Math.max(...times).toFixed(2) : "-";
table.push({ profile: profile.label, ok: `${ok}/${matching.length}`, avg: `${avg}s`, max: `${max}s` });
}
console.table(table);
}
function displayReceivedRawString(raw) {
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
updateStatus("stress-test-status",
`Done: ${succeeded}/${stressTest.count} OK, ${failed} failed (${totalElapsed}s)`,
failed > 0 ? "red" : "green"
);
document.querySelector("#stress-test-button").disabled = false;
stressTest = null;
}
}
main();
+1 -1
View File
@@ -26,7 +26,7 @@
"../go-mix-conn/build": {},
"../pkg": {
"name": "@nymproject/mix-fetch-wasm",
"version": "1.4.2",
"version": "1.4.3",
"license": "Apache-2.0"
},
"node_modules/@discoveryjs/json-ext": {
+30 -19
View File
@@ -206,30 +206,33 @@ async function handleFetchPayload(target) {
}
}
async function handlePostPayload(url, body) {
async function handleStressTestFetch(id, url, label) {
if (!mixFetchReady) {
sendLog('MixFetch not ready yet', 'error');
return;
}
const args = {
method: 'POST',
mode: "unsafe-ignore-cors",
headers: {
'Content-Type': 'application/json',
},
body: body,
};
const tag = `[stress #${id} ${label}]`;
const start = performance.now();
const args = { mode: "unsafe-ignore-cors" };
try {
sendLog(`POST request to: ${url}`);
sendLog(`POST body: ${body}`);
const mixFetchRes = await mixFetch(url, args);
sendLog('POST completed');
await logFetchResult(mixFetchRes);
sendLog(`${tag} Fetching: ${url}`);
const res = await mixFetch(url, args);
const text = await res.text();
const elapsed = ((performance.now() - start) / 1000).toFixed(2);
sendLog(`${tag} ${res.status} OK in ${elapsed}s (${text.length} bytes)`);
self.postMessage({
kind: 'StressTestFetchResult',
args: { id, ok: true, status: res.status, elapsed, textLength: text.length, body: text },
});
} catch (e) {
sendLog('POST request failure: ' + e, 'error');
console.error("mix fetch POST request failure: ", e);
const elapsed = ((performance.now() - start) / 1000).toFixed(2);
sendLog(`${tag} FAILED in ${elapsed}s: ${e}`, 'error');
self.postMessage({
kind: 'StressTestFetchResult',
args: { id, ok: false, elapsed, error: String(e) },
});
}
}
@@ -247,9 +250,17 @@ function setupMessageHandler() {
await handleFetchPayload(target);
break;
}
case 'PostPayload': {
const { url, body } = event.data.args;
await handlePostPayload(url, body);
case 'SetGoTimeout': {
const { timeoutMs } = event.data.args;
sendLog(`Setting Go-side request timeout to ${timeoutMs}ms`);
self.__go_rs_bridge__.goWasmSetMixFetchRequestTimeout(timeoutMs);
break;
}
case 'StressTestFetch': {
const { id, url, label } = event.data.args;
// NOT awaited — each request runs independently,
// just like separate callers in a real app
handleStressTestFetch(id, url, label);
break;
}
}
+1 -1
View File
@@ -238,7 +238,7 @@
fastq "^1.6.0"
"@nymproject/mix-fetch-wasm@file:../pkg":
version "1.4.2"
version "1.4.3"
resolved "file:../pkg"
"@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.6.1":