Update mixfetch example

This commit is contained in:
mfahampshire
2026-04-26 16:36:20 +01:00
parent 4bb0b4f283
commit 266ef7c3c9
2 changed files with 111 additions and 110 deletions
+99 -98
View File
@@ -153,115 +153,116 @@ export const MixFetch = () => {
error: `Error: ${errorMsg}`,
};
const statusColor: Record<typeof status, string> = {
idle: "gray",
idle: "#9e9e9e",
starting: "orange",
ready: "green",
error: "red",
ready: "#85E89D",
error: "#ff6b6b",
};
return (
<div style={{ marginTop: "1rem" }}>
{/* --- Start MixFetch Section --- */}
<Paper sx={{ p: 2, mb: 2 }} variant="outlined">
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="contained"
disabled={status === "starting" || status === "ready"}
onClick={handleStart}
<Box sx={{ mt: 2 }}>
<Paper sx={{ p: 3 }}>
<Stack spacing={3}>
{/* --- Start MixFetch Section --- */}
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="contained"
disabled={status === "starting" || status === "ready"}
onClick={handleStart}
>
Start MixFetch
</Button>
{status === "starting" && <CircularProgress size={20} />}
<Typography
fontFamily="monospace"
fontSize="small"
sx={{ color: statusColor[status] }}
>
{statusText[status]}
</Typography>
</Stack>
{/* --- Fetch Controls (disabled until ready) --- */}
<Box
sx={{
opacity: isReady ? 1 : 0.5,
pointerEvents: isReady ? "auto" : "none",
}}
>
Start MixFetch
</Button>
{status === "starting" && <CircularProgress size={20} />}
<Typography
fontFamily="monospace"
fontSize="small"
sx={{ color: statusColor[status] }}
>
{statusText[status]}
</Typography>
{/* Single fetch */}
<Stack direction="row" spacing={2}>
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
size="small"
/>
<Button
variant="outlined"
disabled={busy}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={2}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
{/* Concurrent fetch demo */}
<Box mt={3}>
<strong>Concurrent Requests</strong>
<Box mt={1}>
<Button
variant="outlined"
disabled={concurrentBusy}
onClick={handleConcurrentFetch}
>
Send 5 Concurrent Requests (posts/1-5)
</Button>
</Box>
</Box>
{concurrentBusy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{concurrentResults.length > 0 && (
<Paper sx={{ p: 2, mt: 2 }} elevation={4}>
{concurrentResults.map((result, i) => (
<Typography key={i} fontFamily="monospace" fontSize="small">
{result}
</Typography>
))}
</Paper>
)}
</Box>
</Stack>
</Paper>
{/* --- Fetch Controls (disabled until ready) --- */}
<Box
sx={{
opacity: isReady ? 1 : 0.5,
pointerEvents: isReady ? "auto" : "none",
}}
>
{/* Single fetch */}
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={2}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
{/* Concurrent fetch demo */}
<Box mt={3}>
<strong>Concurrent Requests</strong>
<Box mt={1}>
<Button
variant="outlined"
disabled={concurrentBusy}
onClick={handleConcurrentFetch}
>
Send 5 Concurrent Requests (posts/1-5)
</Button>
</Box>
</Box>
{concurrentBusy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{concurrentResults.length > 0 && (
<Paper sx={{ p: 2, mt: 2 }} elevation={4}>
{concurrentResults.map((result, i) => (
<Typography key={i} fontFamily="monospace" fontSize="small">
{result}
</Typography>
))}
</Paper>
)}
</Box>
{/* --- Log Panel --- */}
{logs.length > 0 && (
<Paper
ref={logContainerRef}
sx={{ p: 2, mt: 3, maxHeight: 200, overflow: "auto" }}
variant="outlined"
sx={{ p: 2, mt: 2, maxHeight: 200, overflow: "auto" }}
>
<strong>Log</strong>
{logs.map((entry, i) => (
@@ -276,6 +277,6 @@ export const MixFetch = () => {
))}
</Paper>
)}
</div>
</Box>
);
};
@@ -1,6 +1,6 @@
---
title: "mixFetch Example: Private HTTP Requests"
description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, WSS gateways, and usage examples."
description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, TLS configuration, and usage examples."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-15"
@@ -14,13 +14,9 @@ An easy way to secure parts or all of your web app is to replace calls to [`fetc
Things to be aware of:
- CA certificates in `mixFetch` are periodically updated. If you get a certificate error, the root certificate you need might not be valid yet. [Send a PR](https://github.com/nymtech/nym/pulls) if you need changes to the certificates.
- If you are using `mixFetch` in a web app with HTTPS, you will need to use a gateway that has Secure Websockets (WSS) to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error.
- `mixFetch` supports concurrent requests (up to 10) to the same or different URLs.
<Callout type="info">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. You need to select a Gateway that has WSS from [Harbourmaster](https://harbourmaster.nymtech.net/).
</Callout>
- **CA certificates** are bundled into the WASM binary at build time. They're updated with each SDK release — if you hit a certificate error, update to the latest `@nymproject/mix-fetch-full-fat` version.
- **HTTPS and WSS.** When serving your app over HTTPS, the mixnet connection must also use Secure WebSockets to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. Set `forceTls: true` in your `SetupMixFetchOps` config (see below) and the SDK will automatically select a WSS-capable gateway.
- `mixFetch` supports **concurrent requests** (up to 10) to the same or different URLs.
## Environment Setup
@@ -41,30 +37,34 @@ npm run dev
## Installation
```bash
npm install @nymproject/mix-fetch-full-fat
npm install @nymproject/mix-fetch-full-fat @mui/material @emotion/react @emotion/styled
```
The MUI packages are used by the example UI below. If you only need `mixFetch` itself, install just `@nymproject/mix-fetch-full-fat`.
## Configuration
```ts
import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat';
const mixFetchOptions: SetupMixFetchOps = {
clientId: "docs-mixfetch-demo",
clientId: "my-app",
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
forceTls: true, // force WSS
forceTls: true, // use Secure WebSockets (required when serving over HTTPS)
};
```
`preferredGateway` is optional — if omitted, the SDK auto-selects a gateway. You can pin a specific one via [Harbourmaster](https://harbourmaster.nymtech.net/).
## Full Example
This example shows explicit initialization via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel.
<Callout type="info">
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle.
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle.
</Callout>
```tsx