Revert formatting changes to make PR less noisy

This commit is contained in:
mfahampshire
2026-04-01 12:18:55 +01:00
parent b0f5945ba8
commit 9f013aaab0
2 changed files with 467 additions and 471 deletions
+268 -268
View File
@@ -15,226 +15,226 @@
// ─── 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) 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;
}
};
}
startMixFetch = (preferredGateway, setupOpts) => {
this.worker.postMessage({ kind: "StartMixFetch", args: { preferredGateway, setupOpts } });
};
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 },
});
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;
}
};
}
};
startMixFetch = (preferredGateway, setupOpts) => {
this.worker.postMessage({ kind: 'StartMixFetch', args: { preferredGateway, setupOpts } });
};
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 },
});
}
};
}
// ─── Startup ────────────────────────────────────────────────────────────────
let client = null;
const DEFAULT_GATEWAY = "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1";
const DEFAULT_GATEWAY = 'q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1';
async function main() {
client = new WebWorkerClient();
client = new WebWorkerClient();
document.querySelector("#start-mixfetch").onclick = () => {
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;
const setupOpts = {
forceTls: document.getElementById("opt-force-tls").checked,
clientId: document.getElementById("opt-client-id").value,
disablePoisson: document.getElementById("opt-disable-poisson").checked,
disableCover: document.getElementById("opt-disable-cover").checked,
requestTimeoutMs: parseInt(document.getElementById("opt-request-timeout").value, 10),
const setupOpts = {
forceTls: document.getElementById('opt-force-tls').checked,
clientId: document.getElementById('opt-client-id').value,
disablePoisson: document.getElementById('opt-disable-poisson').checked,
disableCover: document.getElementById('opt-disable-cover').checked,
requestTimeoutMs: parseInt(document.getElementById('opt-request-timeout').value, 10),
};
document.querySelector('#start-mixfetch').disabled = true;
document.querySelectorAll('input[name="gateway-mode"]').forEach((r) => (r.disabled = true));
updateStatus('mixfetch-status', 'Starting...', 'orange');
// Sync the stress-test Go timeout to match the configured request timeout
document.getElementById('stress-go-timeout').value = setupOpts.requestTimeoutMs;
console.log(`Starting MixFetch (${gatewayMode} gateway${preferredGateway ? `: ${preferredGateway}` : ''})...`);
console.log('Setup options:', setupOpts);
client.startMixFetch(preferredGateway, setupOpts);
};
document.querySelector("#start-mixfetch").disabled = true;
document.querySelectorAll('input[name="gateway-mode"]').forEach((r) => (r.disabled = true));
updateStatus("mixfetch-status", "Starting...", "orange");
document.querySelector('#fetch-button-1').onclick = () => doFetch(1);
document.querySelector('#fetch-button-2').onclick = () => doFetch(2);
// Sync the stress-test Go timeout to match the configured request timeout
document.getElementById("stress-go-timeout").value = setupOpts.requestTimeoutMs;
console.log(`Starting MixFetch (${gatewayMode} gateway${preferredGateway ? `: ${preferredGateway}` : ""})...`);
console.log("Setup options:", setupOpts);
client.startMixFetch(preferredGateway, setupOpts);
};
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, goTimeoutMs);
stressTest = {
count,
mode,
startTime: performance.now(),
results: [],
completionOrder: [],
profiles: {},
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';
};
for (const req of requests) {
stressTest.profiles[req.id] = { label: req.label, bytes: req.bytes };
}
initStressTracker(requests);
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);
console.log(`%c=== STRESS TEST: ${count} requests, ${mode} mode, timeout=${goTimeoutMs}ms ===`, "font-weight: bold");
document.querySelector('#stress-test-button').disabled = true;
updateStatus('stress-test-status', 'Running...', 'orange');
client.setGoTimeout(goTimeoutMs);
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 requests = generateStressRequests(count, mode, goTimeoutMs);
stressTest = {
count,
mode,
startTime: performance.now(),
results: [],
completionOrder: [],
profiles: {},
};
for (const req of requests) {
stressTest.profiles[req.id] = { label: req.label, bytes: req.bytes };
}
client.doStressTest(requests);
};
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);
}
client.doStressTest(requests);
};
}
// ─── UI helpers ─────────────────────────────────────────────────────────────
function updateStatus(elementId, text, color) {
const el = document.getElementById(elementId);
el.textContent = text;
el.style.color = color;
const el = document.getElementById(elementId);
el.textContent = text;
el.style.color = color;
}
function onMixFetchReady() {
updateStatus("mixfetch-status", "Ready", "green");
document.getElementById("fetch-controls").disabled = false;
console.log("%cMixFetch ready!", "color: green; font-weight: bold");
updateStatus('mixfetch-status', 'Ready', 'green');
document.getElementById('fetch-controls').disabled = false;
console.log('%cMixFetch ready!', 'color: green; font-weight: bold');
}
function onMixFetchError(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);
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;
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 url = document.getElementById(`fetch_payload_${id}`).value;
appendFetchLog(`GET ${url}`);
console.log(`GET ${url}`);
await client.doFetch(url);
const url = document.getElementById(`fetch_payload_${id}`).value;
appendFetchLog(`GET ${url}`);
console.log(`GET ${url}`);
await client.doFetch(url);
}
// ─── Stress test ────────────────────────────────────────────────────────────
const STRESS_PROFILES = [
{ label: "tiny", bytes: 128 },
{ label: "small", bytes: 1024 },
{ label: "medium", bytes: 10240 },
{ label: "large", bytes: 102400 },
{ label: "xlarge", bytes: 1048576 },
{ label: 'tiny', bytes: 128 },
{ label: 'small', bytes: 1024 },
{ label: 'medium', bytes: 10240 },
{ label: 'large', bytes: 102400 },
{ label: 'xlarge', bytes: 1048576 },
];
function buildDripProfiles(timeoutSec) {
return [
{ label: "safe", dripDuration: Math.round(timeoutSec * 0.50), dripDelay: 0, dripBytes: 100 },
{ label: "boundary", dripDuration: Math.round(timeoutSec * 0.92), dripDelay: 0, dripBytes: 100 },
{ label: "over", dripDuration: Math.round(timeoutSec * 1.08), dripDelay: 0, dripBytes: 100 },
{ label: "slow-start", dripDuration: Math.round(timeoutSec * 0.83), dripDelay: Math.round(timeoutSec * 0.17), dripBytes: 100 },
];
return [
{ label: 'safe', dripDuration: Math.round(timeoutSec * 0.50), dripDelay: 0, dripBytes: 100 },
{ label: 'boundary', dripDuration: Math.round(timeoutSec * 0.92), dripDelay: 0, dripBytes: 100 },
{ label: 'over', dripDuration: Math.round(timeoutSec * 1.08), dripDelay: 0, dripBytes: 100 },
{ label: 'slow-start', dripDuration: Math.round(timeoutSec * 0.83), dripDelay: Math.round(timeoutSec * 0.17), dripBytes: 100 },
];
}
function generateStressRequests(count, mode, timeoutMs) {
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 });
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') {
const dripProfiles = buildDripProfiles(timeoutMs / 1000);
for (let i = 1; i <= count; i++) {
const p = dripProfiles[Math.floor(Math.random() * dripProfiles.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,
});
}
}
} else if (mode === "drip") {
const dripProfiles = buildDripProfiles(timeoutMs / 1000);
for (let i = 1; i <= count; i++) {
const p = dripProfiles[Math.floor(Math.random() * dripProfiles.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;
return requests;
}
let stressTest = null;
@@ -243,125 +243,125 @@ let stressTest = null;
let trackerData = {};
function initStressTracker(requests) {
const tracker = document.getElementById("stress-tracker");
tracker.style.display = "block";
trackerData = {};
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: [] };
// 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++;
}
trackerData[req.label].sent++;
}
renderTracker();
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" : "-";
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 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}`;
}
let timing = '';
if (d.times.length > 0) {
timing = `avg ${avg} / min ${min} / max ${max}`;
}
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>`;
});
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>`;
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>`;
}
function onStressTestFetchResult(result) {
if (!stressTest) return;
if (!stressTest) return;
const profile = stressTest.profiles[result.id];
result.label = profile ? profile.label : "?";
const profile = stressTest.profiles[result.id];
result.label = profile ? profile.label : '?';
stressTest.results.push(result);
stressTest.completionOrder.push(result.id);
stressTest.results.push(result);
stressTest.completionOrder.push(result.id);
// 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 progress = `${stressTest.results.length}/${stressTest.count}`;
const tag = `#${result.id} ${result.label}`;
// Update tracker
const td = trackerData[result.label];
if (td) {
if (result.ok) {
td.ok++;
td.times.push(parseFloat(result.elapsed));
console.log(`%c[${tag}] ${result.status} OK ${result.elapsed}s ${result.textLength}B (${progress})`, 'color: green');
} else {
td.fail++;
}
renderTracker();
}
const progress = `${stressTest.results.length}/${stressTest.count}`;
const tag = `#${result.id} ${result.label}`;
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})`);
}
updateStatus("stress-test-status", progress, "orange");
// 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;
console.log(`%c=== COMPLETE: ${totalElapsed}s | OK ${succeeded}/${stressTest.count} | Failed ${failed}/${stressTest.count} ===`, "font-weight: bold");
console.log("Completion order:", stressTest.completionOrder);
// Per-profile breakdown in console
const profileLabels = Object.keys(trackerData);
const profileList = profileLabels.map((label) => ({ label }));
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);
console.error(`[${tag}] FAIL ${result.elapsed}s ${result.error} (${progress})`);
}
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;
}
updateStatus('stress-test-status', progress, 'orange');
// 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;
console.log(`%c=== COMPLETE: ${totalElapsed}s | OK ${succeeded}/${stressTest.count} | Failed ${failed}/${stressTest.count} ===`, 'font-weight: bold');
console.log('Completion order:', stressTest.completionOrder);
// Per-profile breakdown in console
const profileLabels = Object.keys(trackerData);
const profileList = profileLabels.map((label) => ({ label }));
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);
}
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();
+199 -203
View File
@@ -15,28 +15,28 @@
const RUST_WASM_URL = "mix_fetch_wasm_bg.wasm";
const GO_WASM_URL = "go_conn.wasm";
importScripts("mix_fetch_wasm.js");
importScripts("wasm_exec.js");
importScripts('mix_fetch_wasm.js');
importScripts('wasm_exec.js');
console.log("Initializing worker");
console.log('Initializing worker');
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const {
default_debug,
no_cover_debug,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
ClientStorage,
MixFetchConfig,
send_client_data,
start_new_mixnet_connection,
setupMixFetch,
disconnectMixFetch,
setupMixFetchWithConfig,
mix_fetch_initialised,
finish_mixnet_connection,
default_debug,
no_cover_debug,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
ClientStorage,
MixFetchConfig,
send_client_data,
start_new_mixnet_connection,
setupMixFetch,
disconnectMixFetch,
setupMixFetchWithConfig,
mix_fetch_initialised,
finish_mixnet_connection,
} = wasm_bindgen;
let client = null;
@@ -45,43 +45,43 @@ const go = new Go(); // Defined in wasm_exec.js
var goWasm;
let mixFetchReady = false;
function sendLog(message, level = "info") {
self.postMessage({
kind: "Log",
args: { message, level },
});
function sendLog(message, level = 'info') {
self.postMessage({
kind: 'Log',
args: { message, level },
});
}
function sendReady() {
self.postMessage({ kind: "MixFetchReady" });
self.postMessage({ kind: 'MixFetchReady' });
}
function sendError(error) {
self.postMessage({
kind: "MixFetchError",
args: { error: String(error) },
});
self.postMessage({
kind: 'MixFetchError',
args: { error: String(error) },
});
}
async function logFetchResult(res) {
console.log(res);
let text = await res.text();
console.log("HEADERS: ", ...res.headers);
console.log("STATUS: ", res.status);
console.log("STATUS TEXT: ", res.statusText);
console.log("OK: ", res.ok);
console.log("TYPE: ", res.type);
console.log("URL: ", res.url);
console.log("BODYUSED: ", res.bodyUsed);
console.log("REDIRECTED: ", res.redirected);
console.log("TEXT: ", text);
console.log(res);
let text = await res.text();
console.log("HEADERS: ", ...res.headers);
console.log("STATUS: ", res.status);
console.log("STATUS TEXT: ", res.statusText);
console.log("OK: ", res.ok);
console.log("TYPE: ", res.type);
console.log("URL: ", res.url);
console.log("BODYUSED: ", res.bodyUsed);
console.log("REDIRECTED: ", res.redirected);
console.log("TEXT: ", text);
self.postMessage({
kind: "DisplayString",
args: {
rawString: text,
},
});
self.postMessage({
kind: 'DisplayString',
args: {
rawString: text,
},
});
}
/*
@@ -110,205 +110,201 @@ async function logFetchResult(res) {
*/
async function nativeSetup(preferredGateway, setupOpts = {}) {
sendLog("Setting up MixFetch...");
if (preferredGateway) {
sendLog(`Using preferred gateway: ${preferredGateway}`);
} else {
sendLog("Using random gateway selection");
}
sendLog('Setting up MixFetch...');
if (preferredGateway) {
sendLog(`Using preferred gateway: ${preferredGateway}`);
} else {
sendLog('Using random gateway selection');
}
const {
forceTls = true,
clientId = "my-client",
disablePoisson = true,
disableCover = true,
requestTimeoutMs = 60000,
} = setupOpts;
const {
forceTls = true,
clientId = 'my-client',
disablePoisson = true,
disableCover = true,
requestTimeoutMs = 60000,
} = setupOpts;
const noCoverTrafficOverride = {
traffic: { disableMainPoissonPacketDistribution: disablePoisson },
coverTraffic: { disableLoopCoverTrafficStream: disableCover },
};
const mixFetchOverride = {
requestTimeoutMs,
};
const noCoverTrafficOverride = {
traffic: { disableMainPoissonPacketDistribution: disablePoisson },
coverTraffic: { disableLoopCoverTrafficStream: disableCover },
};
const mixFetchOverride = {
requestTimeoutMs,
};
const opts = {
forceTls,
clientId,
clientOverride: noCoverTrafficOverride,
mixFetchOverride,
};
const opts = {
forceTls,
clientId,
clientOverride: noCoverTrafficOverride,
mixFetchOverride,
};
if (preferredGateway) {
opts.preferredGateway = preferredGateway;
}
if (preferredGateway) {
opts.preferredGateway = preferredGateway;
}
sendLog(
`Setup config: forceTls=${forceTls}, clientId=${clientId}, disablePoisson=${disablePoisson}, disableCover=${disableCover}, timeout=${requestTimeoutMs}ms`
);
sendLog("Calling setupMixFetch...");
await setupMixFetch(opts);
sendLog("setupMixFetch completed");
sendLog(
`Setup config: forceTls=${forceTls}, clientId=${clientId}, disablePoisson=${disablePoisson}, disableCover=${disableCover}, timeout=${requestTimeoutMs}ms`
);
sendLog('Calling setupMixFetch...');
await setupMixFetch(opts);
sendLog('setupMixFetch completed');
}
async function startMixFetch(preferredGateway, setupOpts) {
sendLog("Instantiating MixFetch...");
sendLog('Instantiating MixFetch...');
try {
await nativeSetup(preferredGateway, setupOpts);
mixFetchReady = true;
sendLog("MixFetch client running!");
sendReady();
} catch (e) {
sendLog("Failed to start MixFetch: " + e, "error");
sendError(e);
}
try {
await nativeSetup(preferredGateway, setupOpts);
mixFetchReady = true;
sendLog('MixFetch client running!');
sendReady();
} catch (e) {
sendLog('Failed to start MixFetch: ' + e, 'error');
sendError(e);
}
}
async function handleFetchPayload(target) {
if (!mixFetchReady) {
sendLog("MixFetch not ready yet", "error");
return;
}
if (!mixFetchReady) {
sendLog('MixFetch not ready yet', 'error');
return;
}
const url = target;
const args = { mode: "unsafe-ignore-cors" };
const url = target;
const args = { mode: "unsafe-ignore-cors" };
try {
sendLog(`Fetching: ${url}`);
const mixFetchRes = await mixFetch(url, args);
sendLog("Fetch completed");
await logFetchResult(mixFetchRes);
} catch (e) {
sendLog("Fetch request failure: " + e, "error");
console.error("mix fetch request failure: ", e);
}
try {
sendLog(`Fetching: ${url}`);
const mixFetchRes = await mixFetch(url, args);
sendLog('Fetch completed');
await logFetchResult(mixFetchRes);
} catch (e) {
sendLog('Fetch request failure: ' + e, 'error');
console.error("mix fetch request failure: ", e);
}
}
async function handleStressTestFetch(id, url, label) {
if (!mixFetchReady) {
sendLog("MixFetch not ready yet", "error");
return;
}
if (!mixFetchReady) {
sendLog('MixFetch not ready yet', 'error');
return;
}
const tag = `[stress #${id} ${label}]`;
const start = performance.now();
const args = { mode: "unsafe-ignore-cors" };
const tag = `[stress #${id} ${label}]`;
const start = performance.now();
const args = { mode: "unsafe-ignore-cors" };
try {
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) {
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) },
});
}
try {
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) {
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) },
});
}
}
function setupMessageHandler() {
self.onmessage = async (event) => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "StartMixFetch": {
const { preferredGateway, setupOpts } = event.data.args;
await startMixFetch(preferredGateway, setupOpts);
break;
self.onmessage = async (event) => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'StartMixFetch': {
const { preferredGateway, setupOpts } = event.data.args;
await startMixFetch(preferredGateway, setupOpts);
break;
}
case 'FetchPayload': {
const { target } = event.data.args;
await handleFetchPayload(target);
break;
}
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;
}
}
}
case "FetchPayload": {
const { target } = event.data.args;
await handleFetchPayload(target);
break;
}
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;
}
}
}
};
};
}
// TODO: look into https://www.aaron-powell.com/posts/2019-02-08-golang-wasm-5-compiling-with-webpack/
async function loadGoWasm() {
const resp = await fetch(GO_WASM_URL);
const resp = await fetch(GO_WASM_URL);
if ("instantiateStreaming" in WebAssembly) {
const wasmObj = await WebAssembly.instantiateStreaming(
resp,
go.importObject
);
goWasm = wasmObj.instance;
go.run(goWasm);
} else {
const bytes = await resp.arrayBuffer();
const wasmObj = await WebAssembly.instantiate(bytes, go.importObject);
goWasm = wasmObj.instance;
go.run(goWasm);
}
if ('instantiateStreaming' in WebAssembly) {
const wasmObj = await WebAssembly.instantiateStreaming(resp, go.importObject);
goWasm = wasmObj.instance;
go.run(goWasm);
} else {
const bytes = await resp.arrayBuffer();
const wasmObj = await WebAssembly.instantiate(bytes, go.importObject);
goWasm = wasmObj.instance;
go.run(goWasm);
}
}
function setupRsGoBridge() {
// (note: reason for intermediate `__rs_go_bridge__` object is to decrease global scope bloat
// and to discourage users from trying to call those methods directly)
self.__rs_go_bridge__ = {};
self.__rs_go_bridge__.send_client_data = send_client_data;
self.__rs_go_bridge__.start_new_mixnet_connection =
start_new_mixnet_connection;
self.__rs_go_bridge__.mix_fetch_initialised = mix_fetch_initialised;
self.__rs_go_bridge__.finish_mixnet_connection = finish_mixnet_connection;
// (note: reason for intermediate `__rs_go_bridge__` object is to decrease global scope bloat
// and to discourage users from trying to call those methods directly)
self.__rs_go_bridge__ = {};
self.__rs_go_bridge__.send_client_data = send_client_data;
self.__rs_go_bridge__.start_new_mixnet_connection = start_new_mixnet_connection;
self.__rs_go_bridge__.mix_fetch_initialised = mix_fetch_initialised;
self.__rs_go_bridge__.finish_mixnet_connection = finish_mixnet_connection;
}
async function main() {
sendLog("Worker starting...");
sendLog('Worker starting...');
// load rust WASM package
sendLog("Loading Rust WASM...");
await wasm_bindgen(RUST_WASM_URL);
sendLog("Loaded Rust WASM");
// load rust WASM package
sendLog('Loading Rust WASM...');
await wasm_bindgen(RUST_WASM_URL);
sendLog('Loaded Rust WASM');
// load go WASM package
sendLog("Loading Go WASM...");
await loadGoWasm();
sendLog("Loaded Go WASM");
// load go WASM package
sendLog('Loading Go WASM...');
await loadGoWasm();
sendLog('Loaded Go WASM');
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// sets up better stack traces in case of in-rust panics
set_panic_hook();
setupRsGoBridge();
setupRsGoBridge();
goWasmSetLogging("trace");
goWasmSetLogging("trace");
// Set up message handler (MixFetch will be started on demand)
setupMessageHandler();
// Set up message handler (MixFetch will be started on demand)
setupMessageHandler();
sendLog("Worker ready - click Start MixFetch to begin");
sendLog('Worker ready - click Start MixFetch to begin');
}
// Let's get started!